aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format111
-rw-r--r--.clang-tidy6
-rw-r--r--.github/actions/download-qt/action.yml2
-rw-r--r--.github/actions/download-qtc/action.yml2
-rw-r--r--.github/workflows/docs.yml6
-rw-r--r--.github/workflows/main.yml283
-rw-r--r--.github/workflows/release.yml60
-rw-r--r--VERSION2
-rw-r--r--changelogs/changes-2.1.0.md28
-rw-r--r--changelogs/changes-2.1.1.md8
-rw-r--r--changelogs/changes-2.1.2.md19
-rw-r--r--changelogs/changes-2.2.0.md36
-rw-r--r--changelogs/changes-2.2.1.md12
-rw-r--r--changelogs/changes-2.2.2.md18
-rw-r--r--changelogs/changes-2.3.0.md37
-rw-r--r--changelogs/changes-2.3.1.md17
-rw-r--r--doc/appendix/json-api.qdoc7
-rw-r--r--doc/config/macros.qdocconf2
-rw-r--r--doc/doc.qbs1
-rw-r--r--doc/howtos.qdoc12
-rw-r--r--doc/qbs.qdoc23
-rw-r--r--doc/reference/cli/builtin/cli-build.qdoc2
-rw-r--r--doc/reference/cli/builtin/cli-resolve.qdoc1
-rw-r--r--doc/reference/items/convenience/appleapplicationdiskimage.qdoc7
-rw-r--r--doc/reference/items/language/depends.qdoc9
-rw-r--r--doc/reference/items/language/group.qdoc28
-rw-r--r--doc/reference/items/language/moduleprovider.qdoc24
-rw-r--r--doc/reference/items/language/parameters.qdoc4
-rw-r--r--doc/reference/items/probe/binary-probe.qdoc5
-rw-r--r--doc/reference/items/probe/pkgconfig-probe.qdoc11
-rw-r--r--doc/reference/module-providers/qbspkgconfig-module-provider.qdoc37
-rw-r--r--doc/reference/modules/capnprotocpp-module.qdoc4
-rw-r--r--doc/reference/modules/cpp-module.qdoc5
-rw-r--r--doc/reference/modules/exporter-cmake.qdoc135
-rw-r--r--doc/reference/modules/protobufcpp-module.qdoc43
-rw-r--r--doc/reference/modules/qt-core-module.qdoc50
-rw-r--r--doc/tutorial.qdoc647
-rw-r--r--docker-compose.yml134
-rwxr-xr-xdocker/entrypoint.sh (renamed from docker/focal/entrypoint.sh)0
-rw-r--r--docker/jammy/Dockerfile (renamed from docker/focal/Dockerfile)19
-rw-r--r--docker/jammy/test-android-no-qt.Dockerfile (renamed from docker/focal/test-android-no-qt.Dockerfile)4
-rw-r--r--docker/jammy/test-android.Dockerfile (renamed from docker/focal/test-android.Dockerfile)4
-rw-r--r--docker/jammy/test-baremetal.Dockerfile (renamed from docker/focal/test-baremetal.Dockerfile)4
-rw-r--r--docker/jammy/test-qt4.Dockerfile (renamed from docker/focal/test-qt4.Dockerfile)6
-rw-r--r--docker/jammy/test-qt6-static.Dockerfile (renamed from docker/focal/test-qt6-static.Dockerfile)25
-rw-r--r--examples/app-and-lib/app/app.qbs1
-rw-r--r--examples/cocoa-touch-application/cocoa-touch-application.qbs5
-rw-r--r--examples/code-generator/code-generator.qbs2
-rw-r--r--examples/examples.qbs22
-rw-r--r--examples/exporters/exporters.qbs58
-rw-r--r--examples/exporters/lib_a/lib_a.cpp56
-rw-r--r--examples/exporters/lib_a/lib_a.h56
-rw-r--r--examples/exporters/lib_a/lib_a.qbs67
-rw-r--r--examples/exporters/lib_b/lib_b.cpp61
-rw-r--r--examples/exporters/lib_b/lib_b.h56
-rw-r--r--examples/exporters/lib_b/lib_b.qbs64
-rw-r--r--examples/exporters/qbs/imports/MyLibrary.qbs96
-rw-r--r--examples/grpc/grpc.qbs9
-rw-r--r--examples/protobuf/addressbook_cpp/addressbook_cpp.qbs4
-rw-r--r--qbs-resources/imports/QbsProduct.qbs2
-rw-r--r--qbs.qbs10
-rwxr-xr-xscripts/build-qbs-with-cmake.sh2
-rwxr-xr-xscripts/git-clang-format683
-rwxr-xr-xscripts/run-analyzer.sh4
-rw-r--r--share/CMakeLists.txt17
-rw-r--r--share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs6
-rw-r--r--share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs64
-rw-r--r--share/qbs/imports/qbs/Probes/QmakeProbe.qbs41
-rw-r--r--share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js118
-rw-r--r--share/qbs/imports/qbs/Probes/qmake-probe.js1256
-rw-r--r--share/qbs/imports/qbs/ProviderUtils/provider-utils.js128
-rw-r--r--share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs3
-rw-r--r--share/qbs/imports/qbs/base/ApplicationExtension.qbs8
-rw-r--r--share/qbs/imports/qbs/base/QtModule.qbs (renamed from share/qbs/module-providers/Qt/templates/QtModule.qbs)0
-rw-r--r--share/qbs/imports/qbs/base/QtPlugin.qbs (renamed from share/qbs/module-providers/Qt/templates/QtPlugin.qbs)0
-rw-r--r--share/qbs/module-providers/Qt/provider.qbs10
-rw-r--r--share/qbs/module-providers/Qt/setup-qt.js1436
-rw-r--r--share/qbs/module-providers/Qt/templates/core.qbs18
-rw-r--r--share/qbs/module-providers/Qt/templates/dbus.qbs1
-rw-r--r--share/qbs/module-providers/Qt/templates/gui.qbs1
-rw-r--r--share/qbs/module-providers/Qt/templates/module.qbs2
-rw-r--r--share/qbs/module-providers/Qt/templates/plugin.qbs2
-rw-r--r--share/qbs/module-providers/Qt/templates/qml.qbs1
-rw-r--r--share/qbs/module-providers/Qt/templates/quick.qbs1
-rw-r--r--share/qbs/module-providers/Qt/templates/scxml.qbs1
-rw-r--r--share/qbs/module-providers/__fallback/fallback.qbs2
-rw-r--r--share/qbs/module-providers/qbspkgconfig.qbs236
-rw-r--r--share/qbs/modules/Exporter/cmake/cmakeexporter.js239
-rw-r--r--share/qbs/modules/Exporter/cmake/cmakeexporter.qbs84
-rw-r--r--share/qbs/modules/Exporter/exporter.js66
-rw-r--r--share/qbs/modules/Exporter/qbs/qbsexporter.js34
-rw-r--r--share/qbs/modules/Sanitizers/address/asan.qbs3
-rw-r--r--share/qbs/modules/bundle/BundleModule.qbs13
-rw-r--r--share/qbs/modules/bundle/bundle.js9
-rw-r--r--share/qbs/modules/capnproto/capnprotobase.qbs2
-rw-r--r--share/qbs/modules/capnproto/cpp/capnprotocpp.qbs1
-rw-r--r--share/qbs/modules/codesign/codesign.js90
-rw-r--r--share/qbs/modules/cpp/GenericGCC.qbs4
-rw-r--r--share/qbs/modules/cpp/cpp.js10
-rw-r--r--share/qbs/modules/cpp/gcc.js17
-rw-r--r--share/qbs/modules/cpp/msvc.js19
-rw-r--r--share/qbs/modules/cpp/windows-clang-cl.qbs2
-rw-r--r--share/qbs/modules/cpp/windows-msvc-base.qbs5
-rw-r--r--share/qbs/modules/dmg/DMGModule.qbs7
-rw-r--r--share/qbs/modules/dmg/dmg.js19
-rw-r--r--share/qbs/modules/java/utils.js36
-rw-r--r--share/qbs/modules/protobuf/cpp/protobufcpp.qbs110
-rw-r--r--share/qbs/modules/protobuf/nanopb/nanopb.qbs1
-rw-r--r--share/qbs/modules/xcode/xcode.js15
-rw-r--r--share/qbs/modules/xcode/xcode.qbs3
-rw-r--r--share/share.qbs11
-rwxr-xr-xsrc/3rdparty/python/bin/dmgbuild36
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/biplist/LICENSE25
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/biplist/__init__.py977
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/biplist/qt_attribution.json13
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/__init__.py3
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/badge.py143
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/colors.py498
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/core.py655
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/licensing.py461
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/resources.py359
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/ds_store/__init__.py3
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/mac_alias/__init__.py27
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/mac_alias/bookmark.py665
-rw-r--r--src/3rdparty/python/lib/python2.7/site-packages/mac_alias/utils.py18
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/LICENSE (renamed from src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/LICENSE)0
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/__init__.py5
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/__main__.py58
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/badge.py169
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/colors.py504
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/core.py943
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/licensing.py329
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/qt_attribution.json (renamed from src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/qt_attribution.json)0
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/ds_store/LICENSE (renamed from src/3rdparty/python/lib/python2.7/site-packages/ds_store/LICENSE)0
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/ds_store/__init__.py5
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/ds_store/__main__.py102
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/ds_store/buddy.py (renamed from src/3rdparty/python/lib/python2.7/site-packages/ds_store/buddy.py)276
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/ds_store/qt_attribution.json (renamed from src/3rdparty/python/lib/python2.7/site-packages/ds_store/qt_attribution.json)0
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/ds_store/store.py (renamed from src/3rdparty/python/lib/python2.7/site-packages/ds_store/store.py)657
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/mac_alias/LICENSE (renamed from src/3rdparty/python/lib/python2.7/site-packages/mac_alias/LICENSE)0
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/mac_alias/__init__.py47
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/mac_alias/alias.py (renamed from src/3rdparty/python/lib/python2.7/site-packages/mac_alias/alias.py)593
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/mac_alias/bookmark.py677
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/mac_alias/osx.py (renamed from src/3rdparty/python/lib/python2.7/site-packages/mac_alias/osx.py)785
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/mac_alias/qt_attribution.json (renamed from src/3rdparty/python/lib/python2.7/site-packages/mac_alias/qt_attribution.json)0
-rw-r--r--src/3rdparty/python/lib/python3.9/site-packages/mac_alias/utils.py20
-rw-r--r--src/app/config/configcommandexecutor.cpp85
-rw-r--r--src/app/qbs-create-project/create-project-main.cpp4
-rw-r--r--src/app/qbs-setup-qt/main.cpp2
-rw-r--r--src/app/qbs-setup-toolchains/clangclprobe.cpp20
-rw-r--r--src/app/qbs-setup-toolchains/xcodeprobe.cpp2
-rw-r--r--src/app/qbs/CMakeLists.txt8
-rw-r--r--src/app/qbs/commandlinefrontend.cpp20
-rw-r--r--src/app/qbs/lspserver.cpp629
-rw-r--r--src/app/qbs/lspserver.h (renamed from src/lib/corelib/loader/projecttreebuilder.h)43
-rw-r--r--src/app/qbs/parser/commandlineoption.cpp11
-rw-r--r--src/app/qbs/parser/commandlineoption.h17
-rw-r--r--src/app/qbs/parser/commandlineoptionpool.cpp9
-rw-r--r--src/app/qbs/parser/commandlineoptionpool.h1
-rw-r--r--src/app/qbs/parser/commandlineparser.cpp41
-rw-r--r--src/app/qbs/parser/commandlineparser.h2
-rw-r--r--src/app/qbs/parser/parsercommand.cpp24
-rw-r--r--src/app/qbs/qbs.qbs14
-rw-r--r--src/app/qbs/session.cpp15
-rw-r--r--src/app/qbs/sessionpacket.cpp7
-rw-r--r--src/app/qbs/sessionpacket.h2
-rw-r--r--src/app/qbs/sessionpacketreader.cpp9
-rw-r--r--src/app/qbs/stdinreader.cpp194
-rw-r--r--src/lib/corelib/CMakeLists.txt9
-rw-r--r--src/lib/corelib/api/internaljobs.cpp8
-rw-r--r--src/lib/corelib/api/jobs.cpp2
-rw-r--r--src/lib/corelib/api/project.cpp26
-rw-r--r--src/lib/corelib/api/project.h1
-rw-r--r--src/lib/corelib/api/projectdata.cpp79
-rw-r--r--src/lib/corelib/api/projectdata.h10
-rw-r--r--src/lib/corelib/api/projectfileupdater.cpp4
-rw-r--r--src/lib/corelib/api/runenvironment.cpp10
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.cpp4
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.cpp102
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.h3
-rw-r--r--src/lib/corelib/buildgraph/executor.cpp1
-rw-r--r--src/lib/corelib/buildgraph/inputartifactscanner.cpp22
-rw-r--r--src/lib/corelib/buildgraph/nodetreedumper.cpp2
-rw-r--r--src/lib/corelib/buildgraph/processcommandexecutor.cpp2
-rw-r--r--src/lib/corelib/buildgraph/rulecommands.cpp14
-rw-r--r--src/lib/corelib/buildgraph/rulesapplicator.cpp11
-rw-r--r--src/lib/corelib/buildgraph/transformerchangetracking.cpp2
-rw-r--r--src/lib/corelib/corelib.qbs9
-rw-r--r--src/lib/corelib/jsextensions/jsextension.h12
-rw-r--r--src/lib/corelib/jsextensions/pkgconfigjs.cpp3
-rw-r--r--src/lib/corelib/jsextensions/utilitiesextension.cpp8
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp48
-rw-r--r--src/lib/corelib/language/evaluator.cpp82
-rw-r--r--src/lib/corelib/language/evaluator.h15
-rw-r--r--src/lib/corelib/language/item.cpp133
-rw-r--r--src/lib/corelib/language/item.h97
-rw-r--r--src/lib/corelib/language/itempool.cpp2
-rw-r--r--src/lib/corelib/language/language.cpp132
-rw-r--r--src/lib/corelib/language/language.h52
-rw-r--r--src/lib/corelib/language/moduleproviderinfo.h19
-rw-r--r--src/lib/corelib/language/propertydeclaration.cpp109
-rw-r--r--src/lib/corelib/language/propertydeclaration.h14
-rw-r--r--src/lib/corelib/language/propertymapinternal.h2
-rw-r--r--src/lib/corelib/language/qualifiedid.cpp2
-rw-r--r--src/lib/corelib/language/scriptengine.cpp75
-rw-r--r--src/lib/corelib/language/scriptengine.h11
-rw-r--r--src/lib/corelib/language/value.cpp52
-rw-r--r--src/lib/corelib/language/value.h21
-rw-r--r--src/lib/corelib/loader/astimportshandler.cpp18
-rw-r--r--src/lib/corelib/loader/astpropertiesitemhandler.cpp19
-rw-r--r--src/lib/corelib/loader/astpropertiesitemhandler.h4
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.cpp759
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.h44
-rw-r--r--src/lib/corelib/loader/groupshandler.cpp112
-rw-r--r--src/lib/corelib/loader/groupshandler.h26
-rw-r--r--src/lib/corelib/loader/itemreader.cpp18
-rw-r--r--src/lib/corelib/loader/itemreader.h2
-rw-r--r--src/lib/corelib/loader/itemreaderastvisitor.cpp20
-rw-r--r--src/lib/corelib/loader/itemreaderastvisitor.h6
-rw-r--r--src/lib/corelib/loader/itemreadervisitorstate.cpp116
-rw-r--r--src/lib/corelib/loader/itemreadervisitorstate.h14
-rw-r--r--src/lib/corelib/loader/loaderutils.cpp901
-rw-r--r--src/lib/corelib/loader/loaderutils.h367
-rw-r--r--src/lib/corelib/loader/localprofiles.cpp48
-rw-r--r--src/lib/corelib/loader/localprofiles.h18
-rw-r--r--src/lib/corelib/loader/moduleinstantiator.cpp247
-rw-r--r--src/lib/corelib/loader/moduleinstantiator.h56
-rw-r--r--src/lib/corelib/loader/moduleloader.cpp458
-rw-r--r--src/lib/corelib/loader/moduleloader.h61
-rw-r--r--src/lib/corelib/loader/modulepropertymerger.cpp171
-rw-r--r--src/lib/corelib/loader/modulepropertymerger.h53
-rw-r--r--src/lib/corelib/loader/moduleproviderloader.cpp243
-rw-r--r--src/lib/corelib/loader/moduleproviderloader.h72
-rw-r--r--src/lib/corelib/loader/probesresolver.cpp137
-rw-r--r--src/lib/corelib/loader/probesresolver.h28
-rw-r--r--src/lib/corelib/loader/productitemmultiplexer.cpp123
-rw-r--r--src/lib/corelib/loader/productitemmultiplexer.h42
-rw-r--r--src/lib/corelib/loader/productresolver.cpp1618
-rw-r--r--src/lib/corelib/loader/productresolver.h (renamed from src/lib/corelib/loader/productshandler.h)21
-rw-r--r--src/lib/corelib/loader/productscollector.cpp178
-rw-r--r--src/lib/corelib/loader/productscollector.h1
-rw-r--r--src/lib/corelib/loader/productshandler.cpp336
-rw-r--r--src/lib/corelib/loader/productsresolver.cpp599
-rw-r--r--src/lib/corelib/loader/productsresolver.h47
-rw-r--r--src/lib/corelib/loader/projectresolver.cpp1921
-rw-r--r--src/lib/corelib/loader/projectresolver.h4
-rw-r--r--src/lib/corelib/loader/projecttreebuilder.cpp297
-rw-r--r--src/lib/corelib/logging/categories.cpp1
-rw-r--r--src/lib/corelib/logging/categories.h1
-rw-r--r--src/lib/corelib/logging/logger.cpp2
-rw-r--r--src/lib/corelib/parser/qmljsastvisitor_p.h2
-rw-r--r--src/lib/corelib/parser/qmljsengine_p.h2
-rw-r--r--src/lib/corelib/parser/qmljsglobal_p.h35
-rw-r--r--src/lib/corelib/parser/qmljslexer.cpp6
-rw-r--r--src/lib/corelib/parser/qmljslexer_p.h2
-rw-r--r--src/lib/corelib/parser/qmljsparser_p.h2
-rw-r--r--src/lib/corelib/tools/clangclinfo.cpp4
-rw-r--r--src/lib/corelib/tools/codelocation.cpp40
-rw-r--r--src/lib/corelib/tools/codelocation.h72
-rw-r--r--src/lib/corelib/tools/error.cpp7
-rw-r--r--src/lib/corelib/tools/error.h1
-rw-r--r--src/lib/corelib/tools/executablefinder.cpp2
-rw-r--r--src/lib/corelib/tools/launchersocket.cpp7
-rw-r--r--src/lib/corelib/tools/msvcinfo.cpp1
-rw-r--r--src/lib/corelib/tools/mutexdata.h104
-rw-r--r--src/lib/corelib/tools/persistence.cpp10
-rw-r--r--src/lib/corelib/tools/persistence.h18
-rw-r--r--src/lib/corelib/tools/profiling.cpp8
-rw-r--r--src/lib/corelib/tools/profiling.h2
-rw-r--r--src/lib/corelib/tools/progressobserver.h8
-rw-r--r--src/lib/corelib/tools/qbsassert.h5
-rw-r--r--src/lib/corelib/tools/qttools.h75
-rw-r--r--src/lib/corelib/tools/scripttools.cpp41
-rw-r--r--src/lib/corelib/tools/scripttools.h8
-rw-r--r--src/lib/corelib/tools/settings.cpp21
-rw-r--r--src/lib/corelib/tools/settings.h2
-rw-r--r--src/lib/corelib/tools/settingsmodel.cpp10
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.cpp48
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.h6
-rw-r--r--src/lib/corelib/tools/shellutils.cpp2
-rw-r--r--src/lib/corelib/tools/stringconstants.h1
-rw-r--r--src/lib/msbuild/io/visualstudiosolutionwriter.cpp14
-rw-r--r--src/lib/pkgconfig/pcpackage.cpp71
-rw-r--r--src/lib/pkgconfig/pcpackage.h22
-rw-r--r--src/lib/pkgconfig/pcparser.cpp83
-rw-r--r--src/lib/pkgconfig/pcparser.h1
-rw-r--r--src/lib/pkgconfig/pkgconfig.cpp157
-rw-r--r--src/lib/pkgconfig/pkgconfig.h3
-rw-r--r--src/libexec/qbs_processlauncher/launchersockethandler.cpp7
-rw-r--r--src/plugins/generator/visualstudio/msbuildfiltersproject.cpp245
-rw-r--r--src/plugins/scanner/cpp/Lexer.cpp2
-rw-r--r--src/shared/CMakeLists.txt1
-rw-r--r--src/shared/json/json.cpp2
-rw-r--r--src/shared/lsp/.clang-tidy3
-rw-r--r--src/shared/lsp/CMakeLists.txt57
-rw-r--r--src/shared/lsp/algorithm.h1518
-rw-r--r--src/shared/lsp/basemessage.cpp188
-rw-r--r--src/shared/lsp/basemessage.h48
-rw-r--r--src/shared/lsp/callhierarchy.cpp28
-rw-r--r--src/shared/lsp/callhierarchy.h108
-rw-r--r--src/shared/lsp/client.cpp17
-rw-r--r--src/shared/lsp/client.h94
-rw-r--r--src/shared/lsp/clientcapabilities.cpp122
-rw-r--r--src/shared/lsp/clientcapabilities.h674
-rw-r--r--src/shared/lsp/completion.cpp59
-rw-r--r--src/shared/lsp/completion.h269
-rw-r--r--src/shared/lsp/diagnostics.cpp13
-rw-r--r--src/shared/lsp/diagnostics.h38
-rw-r--r--src/shared/lsp/filepath.h68
-rw-r--r--src/shared/lsp/initializemessages.cpp137
-rw-r--r--src/shared/lsp/initializemessages.h187
-rw-r--r--src/shared/lsp/jsonkeys.h231
-rw-r--r--src/shared/lsp/jsonobject.cpp28
-rw-r--r--src/shared/lsp/jsonobject.h181
-rw-r--r--src/shared/lsp/jsonrpcmessages.cpp107
-rw-r--r--src/shared/lsp/jsonrpcmessages.h415
-rw-r--r--src/shared/lsp/languagefeatures.cpp399
-rw-r--r--src/shared/lsp/languagefeatures.h836
-rw-r--r--src/shared/lsp/languageserverprotocol_global.h14
-rw-r--r--src/shared/lsp/languageserverprotocoltr.h15
-rw-r--r--src/shared/lsp/link.cpp35
-rw-r--r--src/shared/lsp/link.h78
-rw-r--r--src/shared/lsp/lsp.qbs72
-rw-r--r--src/shared/lsp/lsptypes.cpp364
-rw-r--r--src/shared/lsp/lsptypes.h680
-rw-r--r--src/shared/lsp/lsputils.cpp68
-rw-r--r--src/shared/lsp/lsputils.h160
-rw-r--r--src/shared/lsp/messages.cpp45
-rw-r--r--src/shared/lsp/messages.h93
-rw-r--r--src/shared/lsp/predicates.h110
-rw-r--r--src/shared/lsp/progresssupport.cpp54
-rw-r--r--src/shared/lsp/progresssupport.h152
-rw-r--r--src/shared/lsp/semantictokens.cpp141
-rw-r--r--src/shared/lsp/semantictokens.h230
-rw-r--r--src/shared/lsp/servercapabilities.cpp386
-rw-r--r--src/shared/lsp/servercapabilities.h435
-rw-r--r--src/shared/lsp/shutdownmessages.cpp13
-rw-r--r--src/shared/lsp/shutdownmessages.h29
-rw-r--r--src/shared/lsp/textsynchronization.cpp84
-rw-r--r--src/shared/lsp/textsynchronization.h215
-rw-r--r--src/shared/lsp/textutils.cpp126
-rw-r--r--src/shared/lsp/textutils.h46
-rw-r--r--src/shared/lsp/workspace.cpp82
-rw-r--r--src/shared/lsp/workspace.h245
-rw-r--r--src/shared/quickjs/.clang-tidy12
-rw-r--r--src/shared/quickjs/quickjs.c26
-rw-r--r--src/shared/quickjs/quickjs.h5
-rw-r--r--src/src.qbs1
-rw-r--r--tests/CMakeLists.txt2
-rw-r--r--tests/auto/api/testdata/infinite-loop-scanning-scan/file.in0
-rw-r--r--tests/auto/api/testdata/infinite-loop-scanning-scan/infinite-loop.qbs21
-rw-r--r--tests/auto/api/testdata/infinite-loop-scanning-scan/modules/m/m.qbs6
-rw-r--r--tests/auto/api/testdata/infinite-loop-scanning-searchpaths/file.in0
-rw-r--r--tests/auto/api/testdata/infinite-loop-scanning-searchpaths/infinite-loop.qbs21
-rw-r--r--tests/auto/api/testdata/infinite-loop-scanning-searchpaths/modules/m/m.qbs7
-rw-r--r--tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs5
-rw-r--r--tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs6
-rw-r--r--tests/auto/api/testdata/restored-warnings/restored-warnings.qbs27
-rw-r--r--tests/auto/api/tst_api.cpp116
-rw-r--r--tests/auto/auto.qbs1
-rw-r--r--tests/auto/blackbox/CMakeLists.txt9
-rw-r--r--tests/auto/blackbox/blackbox-tutorial.qbs21
-rw-r--r--tests/auto/blackbox/find/find-xcode.qbs4
-rw-r--r--tests/auto/blackbox/testdata-apple/codesign/codesign.qbs12
-rw-r--r--tests/auto/blackbox/testdata-providers/allowed-values/allowed-values.qbs4
-rw-r--r--tests/auto/blackbox/testdata-providers/allowed-values/module-providers/provider.qbs14
-rw-r--r--tests/auto/blackbox/testdata-providers/broken-provider/broken-provider.qbs13
-rw-r--r--tests/auto/blackbox/testdata-providers/broken-provider/module-providers/provider_a.qbs5
-rw-r--r--tests/auto/blackbox/testdata-providers/module-providers/module-providers.qbs3
-rw-r--r--tests/auto/blackbox/testdata-providers/non-eager-provider/module-providers/provider_a.qbs11
-rw-r--r--tests/auto/blackbox/testdata-providers/non-eager-provider/non-eager-provider.qbs13
-rw-r--r--tests/auto/blackbox/testdata-providers/probe-in-module-provider/module-providers/provider_a.qbs11
-rw-r--r--tests/auto/blackbox/testdata-providers/probe-in-module-provider/probe-in-module-provider.qbs1
-rw-r--r--tests/auto/blackbox/testdata-providers/removal-version/module-providers/provider_a.qbs14
-rw-r--r--tests/auto/blackbox/testdata-providers/removal-version/removal-version.qbs12
-rw-r--r--tests/auto/blackbox/testdata-qt/qdoc/qdoc.qbs22
-rw-r--r--tests/auto/blackbox/testdata-qt/qdoc/qdoc.qdoc7
-rw-r--r--tests/auto/blackbox/testdata-qt/qdoc/qdoc.qdocconf13
-rw-r--r--tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs5
-rw-r--r--tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs5
-rw-r--r--tests/auto/blackbox/testdata/exports-cmake/Foo.cpp5
-rw-r--r--tests/auto/blackbox/testdata/exports-cmake/Foo.h16
-rw-r--r--tests/auto/blackbox/testdata/exports-cmake/cmake/CMakeLists.txt7
-rw-r--r--tests/auto/blackbox/testdata/exports-cmake/cmake/main.cpp6
-rw-r--r--tests/auto/blackbox/testdata/exports-cmake/exports-cmake.qbs70
-rw-r--r--tests/auto/blackbox/testdata/exports-cmake/find-cmake.qbs46
-rw-r--r--tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs3
-rw-r--r--tests/auto/blackbox/testdata/groups-in-modules/imports/Helper7Base.qbs8
-rw-r--r--tests/auto/blackbox/testdata/groups-in-modules/modules/helper7/helper7.c1
-rw-r--r--tests/auto/blackbox/testdata/groups-in-modules/modules/helper7/helper7.qbs3
-rw-r--r--tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs3
-rw-r--r--tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs3
-rw-r--r--tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs5
-rw-r--r--tests/auto/blackbox/testdata/linkerMode/main.s1
-rw-r--r--tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs7
-rw-r--r--tests/auto/blackbox/testdata/lsp/lsp.qbs11
-rw-r--r--tests/auto/blackbox/testdata/lsp/modules/Prefix/m1/m1.qbs5
-rw-r--r--tests/auto/blackbox/testdata/lsp/modules/Prefix/m2/m2.qbs (renamed from tests/auto/language/testdata/erroneous/modules/prefix1/prefix1.qbs)0
-rw-r--r--tests/auto/blackbox/testdata/lsp/modules/Prefix/m3/m3.qbs2
-rw-r--r--tests/auto/blackbox/testdata/lsp/modules/m/m.qbs2
-rw-r--r--tests/auto/blackbox/testdata/msvc-asm-flags/include/header.inc0
-rw-r--r--tests/auto/blackbox/testdata/msvc-asm-flags/msvc-asm-flags.asm8
-rw-r--r--tests/auto/blackbox/testdata/msvc-asm-flags/msvc-asm-flags.qbs6
-rw-r--r--tests/auto/blackbox/testdata/path-list-in-probe/main.cpp4
-rw-r--r--tests/auto/blackbox/testdata/path-list-in-probe/path-list-in-probe.qbs18
-rw-r--r--tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs4
-rw-r--r--tests/auto/blackbox/testdata/property-assignment-on-non-present-module/property-assignment-on-non-present-module.qbs4
-rw-r--r--tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs3
-rw-r--r--tests/auto/blackbox/testdata/protobuf/addressbook_nanopb.qbs1
-rw-r--r--tests/auto/blackbox/testdata/protobuf/create-proto-library.qbs1
-rw-r--r--tests/auto/blackbox/testdata/protobuf/import.qbs1
-rw-r--r--tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs1
-rw-r--r--tests/auto/blackbox/testdata/qbs-config-import-export/config.json11
-rw-r--r--tests/auto/blackbox/testdata/qbs-config-import-export/config.txt4
-rw-r--r--tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs5
-rw-r--r--tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication-lib.cpp3
-rw-r--r--tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication-main.cpp5
-rw-r--r--tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication.qbs47
-rw-r--r--tests/auto/blackbox/testdata/run-multiplexed/main.cpp1
-rw-r--r--tests/auto/blackbox/testdata/run-multiplexed/run-multiplexed.qbs21
-rw-r--r--tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs3
-rw-r--r--tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs5
-rw-r--r--tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs5
-rw-r--r--tests/auto/blackbox/testdata/versionscript/versionscript.qbs6
-rw-r--r--tests/auto/blackbox/testdata/wildcards-and-change-tracking/nonrecursive/subdir1/file.txt0
-rw-r--r--tests/auto/blackbox/testdata/wildcards-and-change-tracking/nonrecursive/subdir2/file.txt0
-rw-r--r--tests/auto/blackbox/testdata/wildcards-and-change-tracking/recursive1/recursive.txt0
-rw-r--r--tests/auto/blackbox/testdata/wildcards-and-change-tracking/recursive2/recursive.txt0
-rw-r--r--tests/auto/blackbox/testdata/wildcards-and-change-tracking/wildcards-and-change-tracking.qbs16
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp581
-rw-r--r--tests/auto/blackbox/tst_blackbox.h15
-rw-r--r--tests/auto/blackbox/tst_blackboxapple.cpp78
-rw-r--r--tests/auto/blackbox/tst_blackboxapple.h6
-rw-r--r--tests/auto/blackbox/tst_blackboxbase.cpp2
-rw-r--r--tests/auto/blackbox/tst_blackboxjava.cpp11
-rw-r--r--tests/auto/blackbox/tst_blackboxjava.h3
-rw-r--r--tests/auto/blackbox/tst_blackboxproviders.cpp105
-rw-r--r--tests/auto/blackbox/tst_blackboxproviders.h7
-rw-r--r--tests/auto/blackbox/tst_blackboxqt.cpp51
-rw-r--r--tests/auto/blackbox/tst_blackboxqt.h2
-rw-r--r--tests/auto/blackbox/tst_blackboxtutorial.cpp72
-rw-r--r--tests/auto/blackbox/tst_blackboxtutorial.h48
-rw-r--r--tests/auto/blackbox/tst_blackboxwindows.cpp2
-rw-r--r--tests/auto/cmdlineparser/tst_cmdlineparser.cpp21
-rw-r--r--tests/auto/language/testdata/erroneous/dependency_cycle3a.qbs11
-rw-r--r--tests/auto/language/testdata/erroneous/frozen-object-list.qbs17
-rw-r--r--tests/auto/language/testdata/erroneous/frozen-object.qbs18
-rw-r--r--tests/auto/language/testdata/erroneous/module-property-binding-in-project.qbs3
-rw-r--r--tests/auto/language/testdata/invalid-prop-on-non-required-module/invalid-prop-on-non-required-module.qbs19
-rw-r--r--tests/auto/language/testdata/invalid-prop-on-non-required-module/modules/dep/dep.qbs1
-rw-r--r--tests/auto/language/testdata/invalid-prop-on-non-required-module/modules/deploader/deploader.qbs7
-rw-r--r--tests/auto/language/testdata/local-profile-as-top-level-profile.qbs7
-rw-r--r--tests/auto/language/testdata/module-name-collisions/complex-collision.qbs4
-rw-r--r--tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/middle1.qbs1
-rw-r--r--tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/suffix1/suffix1.qbs1
-rw-r--r--tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/suffix2/suffix2.qbs1
-rw-r--r--tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle2/suffix/suffix.qbs1
-rw-r--r--tests/auto/language/testdata/module-name-collisions/modules/prefix1/prefix1.qbs2
-rw-r--r--tests/auto/language/testdata/module-name-collisions/modules/prefix1/suffix/suffix.qbs (renamed from tests/auto/language/testdata/erroneous/modules/prefix1/suffix/suffix.qbs)0
-rw-r--r--tests/auto/language/testdata/module-name-collisions/modules/prefix2/prefix2.qbs3
-rw-r--r--tests/auto/language/testdata/module-name-collisions/modules/prefix2/suffix/suffix.qbs2
-rw-r--r--tests/auto/language/testdata/module-name-collisions/no-collision1.qbs4
-rw-r--r--tests/auto/language/testdata/module-name-collisions/no-collision2.qbs4
-rw-r--r--tests/auto/language/testdata/module-name-collisions/simple-collision1.qbs (renamed from tests/auto/language/testdata/erroneous/same-module-prefix1.qbs)0
-rw-r--r--tests/auto/language/testdata/module-name-collisions/simple-collision2.qbs (renamed from tests/auto/language/testdata/erroneous/same-module-prefix2.qbs)0
-rw-r--r--tests/auto/language/testdata/module-parameters/module-parameters.qbs35
-rw-r--r--tests/auto/language/testdata/module-parameters/modules/broken/broken.qbs4
-rw-r--r--tests/auto/language/testdata/module-parameters/modules/higher/higher.qbs4
-rw-r--r--tests/auto/language/testdata/module-parameters/modules/highest/highest.qbs7
-rw-r--r--tests/auto/language/testdata/module-parameters/modules/lower/lower.qbs3
-rw-r--r--tests/auto/language/testdata/probes-and-multiplexing.qbs15
-rw-r--r--tests/auto/language/tst_language.cpp339
-rw-r--r--tests/auto/language/tst_language.h9
-rw-r--r--tests/auto/pkgconfig/testdata/lib/pkgconfig/prefix.pc13
-rw-r--r--tests/auto/pkgconfig/testdata/requires-test-merged-static.json22
-rw-r--r--tests/auto/pkgconfig/testdata/requires-test-merged.json19
-rw-r--r--tests/auto/pkgconfig/tst_pkgconfig.cpp79
-rw-r--r--tests/auto/pkgconfig/tst_pkgconfig.h4
-rw-r--r--tests/clang-format-test/CMakeLists.txt4
-rw-r--r--tests/clang-format-test/clang-format-test.qbs5
-rw-r--r--tests/clang-format-test/clang-format.cpp681
-rw-r--r--tests/fuzzy-test/fuzzytester.cpp7
-rw-r--r--tests/lspclient/CMakeLists.txt8
-rw-r--r--tests/lspclient/lspclient.cpp434
-rw-r--r--tests/lspclient/lspclient.qbs10
-rw-r--r--tests/tests.qbs2
-rw-r--r--tutorial/LICENSE49
-rw-r--r--tutorial/chapter-1/app/app.qbs12
-rw-r--r--tutorial/chapter-1/app/main.c9
-rw-r--r--tutorial/chapter-1/myproject.qbs9
-rw-r--r--tutorial/chapter-2/app/app.qbs11
-rw-r--r--tutorial/chapter-2/app/main.c10
-rw-r--r--tutorial/chapter-2/lib/lib.c15
-rw-r--r--tutorial/chapter-2/lib/lib.h11
-rw-r--r--tutorial/chapter-2/lib/lib.qbs28
-rw-r--r--tutorial/chapter-2/myproject.qbs10
-rw-r--r--tutorial/chapter-3/app/app.qbs29
-rw-r--r--tutorial/chapter-3/app/main.c10
-rw-r--r--tutorial/chapter-3/lib/lib.c16
-rw-r--r--tutorial/chapter-3/lib/lib.h11
-rw-r--r--tutorial/chapter-3/lib/lib.qbs26
-rw-r--r--tutorial/chapter-3/lib/lib_global.h23
-rw-r--r--tutorial/chapter-3/myproject.qbs8
-rw-r--r--tutorial/chapter-4/app/app.qbs10
-rw-r--r--tutorial/chapter-4/app/main.c10
-rw-r--r--tutorial/chapter-4/lib/lib.c10
-rw-r--r--tutorial/chapter-4/lib/lib.h8
-rw-r--r--tutorial/chapter-4/lib/lib.qbs13
-rw-r--r--tutorial/chapter-4/lib/lib_global.h18
-rw-r--r--tutorial/chapter-4/myproject.qbs11
-rw-r--r--tutorial/chapter-4/qbs/imports/MyApplication.qbs23
-rw-r--r--tutorial/chapter-4/qbs/imports/MyLibrary.qbs21
-rw-r--r--tutorial/chapter-5/app/app.qbs6
-rw-r--r--tutorial/chapter-5/app/main.c10
-rw-r--r--tutorial/chapter-5/lib/lib.c10
-rw-r--r--tutorial/chapter-5/lib/lib.h8
-rw-r--r--tutorial/chapter-5/lib/lib.qbs9
-rw-r--r--tutorial/chapter-5/lib/lib_global.h18
-rw-r--r--tutorial/chapter-5/myproject.qbs15
-rw-r--r--tutorial/chapter-5/qbs/imports/MyApplication.qbs20
-rw-r--r--tutorial/chapter-5/qbs/imports/MyAutoTest.qbs7
-rw-r--r--tutorial/chapter-5/qbs/imports/MyLibrary.qbs17
-rw-r--r--tutorial/chapter-5/test/test.c22
-rw-r--r--tutorial/chapter-5/test/test.qbs9
-rw-r--r--tutorial/chapter-6/app/app.qbs6
-rw-r--r--tutorial/chapter-6/app/main.c10
-rw-r--r--tutorial/chapter-6/lib/lib.c10
-rw-r--r--tutorial/chapter-6/lib/lib.h8
-rw-r--r--tutorial/chapter-6/lib/lib.qbs9
-rw-r--r--tutorial/chapter-6/lib/lib_global.h18
-rw-r--r--tutorial/chapter-6/myproject.qbs35
-rw-r--r--tutorial/chapter-6/qbs/imports/MyApplication.qbs23
-rw-r--r--tutorial/chapter-6/qbs/imports/MyAutoTest.qbs3
-rw-r--r--tutorial/chapter-6/qbs/imports/MyLibrary.qbs18
-rw-r--r--tutorial/chapter-6/test/test.c18
-rw-r--r--tutorial/chapter-6/test/test.qbs5
-rw-r--r--tutorial/chapter-7/app/app.qbs6
-rw-r--r--tutorial/chapter-7/app/main.c10
-rw-r--r--tutorial/chapter-7/lib/lib.c10
-rw-r--r--tutorial/chapter-7/lib/lib.h8
-rw-r--r--tutorial/chapter-7/lib/lib.qbs60
-rw-r--r--tutorial/chapter-7/lib/lib_global.h18
-rw-r--r--tutorial/chapter-7/myproject.qbs27
-rw-r--r--tutorial/chapter-7/qbs/imports/MyApplication.qbs13
-rw-r--r--tutorial/chapter-7/qbs/imports/MyAutoTest.qbs3
-rw-r--r--tutorial/chapter-7/qbs/imports/MyLibrary.qbs25
-rw-r--r--tutorial/chapter-7/qbs/modules/mybuildconfig/mybuildconfig.qbs26
-rw-r--r--tutorial/chapter-7/test/test.c18
-rw-r--r--tutorial/chapter-7/test/test.qbs5
-rw-r--r--tutorial/chapter-8/app/app.qbs6
-rw-r--r--tutorial/chapter-8/app/main.c10
-rw-r--r--tutorial/chapter-8/lib/lib.c10
-rw-r--r--tutorial/chapter-8/lib/lib.h8
-rw-r--r--tutorial/chapter-8/lib/lib.qbs10
-rw-r--r--tutorial/chapter-8/lib/lib_global.h25
-rw-r--r--tutorial/chapter-8/myproject.qbs27
-rw-r--r--tutorial/chapter-8/qbs/imports/MyApplication.qbs10
-rw-r--r--tutorial/chapter-8/qbs/imports/MyAutoTest.qbs3
-rw-r--r--tutorial/chapter-8/qbs/imports/MyLibrary.qbs26
-rw-r--r--tutorial/chapter-8/qbs/modules/mybuildconfig/mybuildconfig.qbs25
-rw-r--r--tutorial/chapter-8/test/test.c18
-rw-r--r--tutorial/chapter-8/test/test.qbs5
-rw-r--r--tutorial/chapter-9/app/app.qbs9
-rw-r--r--tutorial/chapter-9/app/main.c14
-rw-r--r--tutorial/chapter-9/lib/lib.c10
-rw-r--r--tutorial/chapter-9/lib/lib.h8
-rw-r--r--tutorial/chapter-9/lib/lib.qbs10
-rw-r--r--tutorial/chapter-9/lib/lib_global.h25
-rw-r--r--tutorial/chapter-9/myproject.qbs32
-rw-r--r--tutorial/chapter-9/qbs/imports/MyApplication.qbs10
-rw-r--r--tutorial/chapter-9/qbs/imports/MyAutoTest.qbs3
-rw-r--r--tutorial/chapter-9/qbs/imports/MyLibrary.qbs26
-rw-r--r--tutorial/chapter-9/qbs/modules/mybuildconfig/mybuildconfig.qbs30
-rw-r--r--tutorial/chapter-9/test/test.c18
-rw-r--r--tutorial/chapter-9/test/test.qbs5
-rw-r--r--tutorial/chapter-9/version-header/version-header.qbs57
-rw-r--r--tutorial/chapter-9/version-header/version.h.in9
-rw-r--r--tutorial/tutorial.qbs5
578 files changed, 31432 insertions, 12530 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 000000000..d95a1bafe
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,111 @@
+# .clang-format for Qbs
+# based on https://github.com/qt-creator/qt-creator/blob/v12.0.1/.clang-format
+#
+# This is for clang-format >= 5.0.
+#
+# Use ../../tests/clang-format-test/clang-format-test.cpp for documenting problems
+# or testing changes.
+#
+---
+Language: Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: DontAlign
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: true
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: false
+BreakBeforeBinaryOperators: All
+BreakBeforeBraces: Custom
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 100
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - forever # avoids { wrapped to next line
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeCategories:
+ - Regex: '^<Q.*'
+ Priority: 200
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+InsertBraces: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+# Do not add QT_BEGIN_NAMESPACE/QT_END_NAMESPACE as this will indent lines in between.
+MacroBlockBegin: ""
+MacroBlockEnd: ""
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 88
+PenaltyBreakBeforeFirstCallParameter: 50
+PenaltyBreakComment: 500
+PenaltyBreakFirstLessLess: 400
+PenaltyBreakString: 600
+PenaltyExcessCharacter: 500
+PenaltyReturnTypeOnItsOwnLine: 300
+PointerAlignment: Right
+ReflowComments: false
+SortIncludes: CaseSensitive
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: c++17
+TabWidth: 4
+UseTab: Never
diff --git a/.clang-tidy b/.clang-tidy
index e737372e9..92e6eeefe 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -2,6 +2,7 @@
Checks: >
-*,
bugprone-*,
+ -bugprone-easily-swappable-parameters,
-bugprone-narrowing-conversions,
-bugprone-throw-keyword-missing,
cppcoreguidelines-interfaces-global-init,
@@ -12,7 +13,7 @@ Checks: >
google-build-namespaces,
google-global-names-in-headers,
google-objc-*,
- google-readability-casting,
+ -google-readability-casting,
google-readability-namespace-comments,
google-runtime-operator,
misc-definitions-in-headers,
@@ -70,6 +71,3 @@ WarningsAsErrors: >
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
-CheckOptions:
-...
-
diff --git a/.github/actions/download-qt/action.yml b/.github/actions/download-qt/action.yml
index b6785654b..039f29ca6 100644
--- a/.github/actions/download-qt/action.yml
+++ b/.github/actions/download-qt/action.yml
@@ -4,7 +4,7 @@ inputs:
version:
description: 'Qt version'
required: false
- default: '6.2.4'
+ default: '6.6.0'
target:
description: 'Qt target (desktop, ios, android)'
required: false
diff --git a/.github/actions/download-qtc/action.yml b/.github/actions/download-qtc/action.yml
index 542a2cb28..a04681df4 100644
--- a/.github/actions/download-qtc/action.yml
+++ b/.github/actions/download-qtc/action.yml
@@ -4,7 +4,7 @@ inputs:
version:
description: 'Qt Creator version'
required: false
- default: '9.0.1'
+ default: '11.0.3'
runs:
using: "composite"
steps:
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 248baf5f7..d820a03aa 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -27,10 +27,10 @@ jobs:
WITH_TESTS: 0
steps:
- uses: actions/checkout@v1
- - name: Pull the Focal Image
- run: docker-compose pull focal
+ - name: Pull the Docker Image
+ run: docker-compose pull jammy
- name: Build Qbs Docs
- run: docker-compose run focal ${{ matrix.config.script }}
+ run: docker-compose run jammy ${{ matrix.config.script }}
- name: Upload artifacts
uses: 'actions/upload-artifact@v2'
with:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3006be6c7..71c1ae4ac 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -16,14 +16,16 @@ jobs:
matrix:
config:
- {
- name: 'Build on Linux (gcc)',
+ name: 'Build on Linux (Jammy, gcc)',
+ image: 'jammy-qt6',
options: 'modules.cpp.compilerWrapper:ccache
modules.qbs.debugInformation:true
modules.qbsbuildconfig.enableBundledQt:true
- products.qbs_archive.targetName:qbs-linux-${{ github.run_id }}
+ products.qbs_archive.targetName:qbs-linux-jammy-${{ github.run_id }}
products.qbs_archive.includeTests:true',
script: './scripts/build-qbs-with-qbs.sh',
- cacheid: 'gcc-qt6',
+ cacheid: 'gcc-qt6-jammy',
+ suffix: 'linux-jammy',
}
env:
BUILD_OPTIONS: ${{ matrix.config.options }}
@@ -32,28 +34,24 @@ jobs:
- uses: actions/checkout@v1
- name: Create .ccache dir
run: mkdir -p ~/.ccache
- - name: prepare timestamp
- id: get-timestamp
- run: echo ::set-output name=timestamp::$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- name: ccache cache files
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/.ccache
- key: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache-${{ steps.get-timestamp.outputs.timestamp }}
- restore-keys: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache-
+ key: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache
- name: Pull the Docker Image
- run: docker-compose pull focal-qt6
+ run: docker-compose pull ${{ matrix.config.image }}
- name: Print ccache stats
- run: docker-compose run focal-qt6 ccache -s
+ run: docker-compose run ${{ matrix.config.image }} ccache -s
- name: Build Qbs
- run: docker-compose run focal-qt6 ${{ matrix.config.script }}
+ run: docker-compose run ${{ matrix.config.image }} ${{ matrix.config.script }}
- name: Print ccache stats
- run: docker-compose run focal-qt6 ccache -s
+ run: docker-compose run ${{ matrix.config.image }} ccache -s
- name: Upload artifacts
- uses: 'actions/upload-artifact@v2'
+ uses: 'actions/upload-artifact@v4'
with:
- name: qbs-linux-${{ github.run_id }}.tar.gz
- path: release/qbs-linux-${{ github.run_id }}.tar.gz
+ name: qbs-${{ matrix.config.suffix }}-${{ github.run_id }}.tar.gz
+ path: release/qbs-${{ matrix.config.suffix }}-${{ github.run_id }}.tar.gz
build-linux-extra:
name: ${{ matrix.config.name }}
@@ -66,19 +64,19 @@ jobs:
- {
name: 'Build on Linux (clang_tidy)',
script: './scripts/run-analyzer.sh',
- image: 'focal-qt6',
+ image: 'jammy-qt6',
options: 'profile:qt-clang_64 modules.cpp.compilerWrapper:ccache',
cacheid: 'clang',
}
- {
name: 'Build on Linux (CMake)',
script: './scripts/build-qbs-with-cmake.sh',
- image: 'focal-qt6',
+ image: 'jammy-qt6',
cacheid: 'cmake',
}
- {
name: 'Build on Linux (gcc, ASAN)',
- image: 'focal-qt6',
+ image: 'jammy-qt6',
script: './scripts/build-qbs-with-qbs.sh',
options: 'modules.cpp.compilerWrapper:ccache
modules.qbsbuildconfig.enableAddressSanitizer:true
@@ -88,7 +86,7 @@ jobs:
}
- {
name: 'Build on Linux (gcc, UBSAN)',
- image: 'focal-qt6',
+ image: 'jammy-qt6',
script: './scripts/build-qbs-with-qbs.sh',
options: 'modules.cpp.compilerWrapper:ccache
modules.qbsbuildconfig.enableUbSanitizer:true
@@ -98,7 +96,7 @@ jobs:
}
- {
name: 'Build on Linux (gcc, Qt 5.15)',
- image: 'focal',
+ image: 'jammy-qt5',
script: './scripts/build-qbs-with-qbs.sh',
options: 'modules.cpp.compilerWrapper:ccache
modules.qbs.debugInformation:true
@@ -112,15 +110,11 @@ jobs:
- uses: actions/checkout@v1
- name: Create .ccache dir
run: mkdir -p ~/.ccache
- - name: prepare timestamp
- id: get-timestamp
- run: echo ::set-output name=timestamp::$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- name: ccache cache files
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/.ccache
- key: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache-${{ steps.get-timestamp.outputs.timestamp }}
- restore-keys: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache-
+ key: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache
- name: Pull the Docker Image
run: docker-compose pull ${{ matrix.config.image }}
- name: Print ccache stats
@@ -130,9 +124,25 @@ jobs:
- name: Print ccache stats
run: docker-compose run ${{ matrix.config.image }} ccache -s
+ clang-format:
+ name: Check Style
+ runs-on: macos-13
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ steps:
+ - uses: actions/checkout@v1
+ - name: Update PATH
+ run: echo "$(brew --prefix llvm@15)/bin" >> ${GITHUB_PATH}
+ - name: Run clang-format
+ run: |
+ git diff -U0 --no-color --relative HEAD^ | $(brew --prefix llvm@15)/share/clang/clang-format-diff.py -i -p1 -regex ".*\.(cxx|cpp|hpp|h)" -v
+ - name: Check diff
+ run: git diff --exit-code
+
build-macos:
name: Build on macOS
- runs-on: macos-11
+ runs-on: macos-12
timeout-minutes: 60
env:
BUILD_OPTIONS: |
@@ -147,15 +157,11 @@ jobs:
- uses: actions/checkout@v1
- name: Create .ccache dir
run: mkdir -p ~/.ccache
- - name: prepare timestamp
- id: get-timestamp
- run: echo ::set-output name=timestamp::$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- name: ccache cache files
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/.ccache
- key: ${{ runner.os }}-ccache-${{ steps.get-timestamp.outputs.timestamp }}
- restore-keys: ${{ runner.os }}-ccache-
+ key: ${{ runner.os }}-ccache
- name: Install required packages
run: |
brew install ccache p7zip
@@ -170,7 +176,7 @@ jobs:
run: |
qbs setup-toolchains --detect
qbs setup-qt --detect
- qbs config profiles.qt.baseProfile xcode-macosx-x86_64
+ qbs config profiles.qt.baseProfile xcode_13_2_1-macosx-x86_64
qbs config defaultProfile qt
qbs config --list
- name: Print ccache stats
@@ -180,7 +186,7 @@ jobs:
- name: Print ccache stats
run: ccache -s
- name: Upload artifacts
- uses: 'actions/upload-artifact@v2'
+ uses: 'actions/upload-artifact@v4'
with:
name: qbs-macos-${{ github.run_id }}.tar.gz
path: release/qbs-macos-${{ github.run_id }}.tar.gz
@@ -201,18 +207,13 @@ jobs:
QT_ASSUME_STDERR_HAS_CONSOLE: 1
steps:
- uses: actions/checkout@v1
- - name: prepare timestamp
- id: get-timestamp
- run: echo ::set-output name=timestamp::$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- shell: bash
- name: clcache cache files
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/clcache
- key: ${{ runner.os }}-msvc-clcache-${{ steps.get-timestamp.outputs.timestamp }}
- restore-keys: ${{ runner.os }}-msvc-clcache-
+ key: ${{ runner.os }}-msvc-clcache
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Install required packages
@@ -230,6 +231,7 @@ jobs:
run: |
qbs setup-toolchains --detect
qbs setup-qt $(which qmake).exe qt
+ qbs config profiles.qt.baseProfile MSVC2022-1-x86_x64
qbs config defaultProfile qt
qbs config --list
shell: bash
@@ -241,7 +243,7 @@ jobs:
- name: Print clcache stats
run: clcache -s
- name: Upload artifacts
- uses: 'actions/upload-artifact@v2'
+ uses: 'actions/upload-artifact@v4'
with:
name: qbs-windows-${{ github.run_id }}.zip
path: release/qbs-windows-${{ github.run_id }}.zip
@@ -262,18 +264,13 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}\ccache
steps:
- uses: actions/checkout@v1
- - name: prepare timestamp
- id: get-timestamp
- run: echo ::set-output name=timestamp::$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- shell: bash
- name: ccache cache files
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ${{ github.workspace }}/ccache
- key: ${{ runner.os }}-mingw-ccache-${{ steps.get-timestamp.outputs.timestamp }}
- restore-keys: ${{ runner.os }}-mingw-ccache-
+ key: ${{ runner.os }}-mingw-ccache
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Install required packages
@@ -305,7 +302,7 @@ jobs:
- name: Print ccache stats
run: ccache -s
- name: Upload artifacts
- uses: 'actions/upload-artifact@v2'
+ uses: 'actions/upload-artifact@v4'
with:
name: qbs-windows-mingw-${{ github.run_id }}.zip
path: release/qbs-windows-mingw-${{ github.run_id }}.zip
@@ -320,80 +317,79 @@ jobs:
matrix:
config:
- {
- name: 'Run Linux tests (gcc)',
- image: 'focal',
+ name: 'Run Linux tests (gcc, Qt 6)',
+ image: 'jammy-qt6',
+ suffix: 'linux-jammy',
profile: 'qt-gcc_64',
script: './scripts/test-qbs.sh',
}
- {
- name: 'Run Linux tests (clang)',
- image: 'focal',
+ name: 'Run Linux tests (clang, Qt 6)',
+ image: 'jammy-qt6',
+ suffix: 'linux-jammy',
profile: 'qt-clang_64',
script: './scripts/test-qbs.sh',
}
- {
- name: 'Run Linux tests (gcc, Qt 6.2)',
- image: 'focal-qt6',
+ name: 'Run Linux tests (gcc, Qt 5)',
+ image: 'jammy-qt5',
+ suffix: 'linux-jammy',
profile: 'qt-gcc_64',
script: './scripts/test-qt.sh',
}
- {
- name: 'Run Linux tests (gcc, Qt 6.3 static)',
- image: 'focal-qt6-static',
+ name: 'Run Linux tests (gcc, Qt 6.5 static)',
+ image: 'jammy-qt6-static',
+ suffix: 'linux-jammy',
profile: 'qt-gcc_64',
script: './scripts/test-qt.sh',
}
- {
name: 'Run Android tests (Qt 5.15)',
- image: 'focal-android-515',
- profile: '',
- script: './scripts/test-qt-for-android.sh',
- }
- - {
- name: 'Run Android tests (Qt 6.2.0)',
- image: 'focal-android-620',
+ image: 'jammy-android-515',
+ suffix: 'linux-jammy',
profile: '',
script: './scripts/test-qt-for-android.sh',
}
- {
- name: 'Run Android tests (Qt 6.3.0)',
- image: 'focal-android-630',
+ name: 'Run Android tests (Qt 6.2)',
+ image: 'jammy-android-62',
+ suffix: 'linux-jammy',
profile: '',
script: './scripts/test-qt-for-android.sh',
}
- {
- name: 'Run Android tests (Qt 6.4.2)',
- image: 'focal-android-642',
- profile: '',
- script: './scripts/test-qt-for-android.sh',
- }
- - {
- name: 'Run Android tests (Qt 6.5.0)',
- image: 'focal-android-650',
+ name: 'Run Android tests (Qt 6.5)',
+ image: 'jammy-android-65',
+ suffix: 'linux-jammy',
profile: '',
script: './scripts/test-qt-for-android.sh',
}
- {
name: 'Run Android tests (ndk r19c)',
- image: 'focal-android-ndk-r19c',
+ image: 'jammy-android-ndk-r19c',
+ suffix: 'linux-jammy',
profile: '',
script: './scripts/test-for-android.sh',
}
- {
name: 'Run Android tests (ndk r21e)',
- image: 'focal-android-ndk-r21e',
+ image: 'jammy-android-ndk-r21e',
+ suffix: 'linux-jammy',
profile: '',
script: './scripts/test-for-android.sh',
}
- {
name: 'Run Android tests (ndk r23)',
- image: 'focal-android-ndk-r23',
+ image: 'jammy-android-ndk-r23',
+ suffix: 'linux-jammy',
profile: '',
script: './scripts/test-for-android.sh',
}
- {
name: 'Run Linux tests (Qt 4.8.7)',
- image: 'focal-qt4',
+ image: 'jammy-qt4',
+ suffix: 'linux-jammy',
profile: '',
script: './scripts/test-qt4.sh',
}
@@ -405,12 +401,12 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Download artifact
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
- name: qbs-linux-${{ github.run_id }}.tar.gz
+ name: qbs-${{ matrix.config.suffix }}-${{ github.run_id }}.tar.gz
path: ./
- name: Unpack artifact
- run: mkdir -p release/install-root/ && tar xzf qbs-linux-${{ github.run_id }}.tar.gz -C release/install-root/
+ run: mkdir -p release/install-root/ && tar xzf qbs-${{ matrix.config.suffix }}-${{ github.run_id }}.tar.gz -C release/install-root/
- name: Pull the Docker Image
run: docker-compose pull ${{ matrix.config.image }}
- name: Run tests
@@ -426,30 +422,30 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Download artifact
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
- name: qbs-linux-${{ github.run_id }}.tar.gz
+ name: qbs-linux-jammy-${{ github.run_id }}.tar.gz
path: ./
- name: Unpack artifact
- run: mkdir -p release/install-root/ && tar xzf qbs-linux-${{ github.run_id }}.tar.gz -C release/install-root/
+ run: mkdir -p release/install-root/ && tar xzf qbs-linux-jammy-${{ github.run_id }}.tar.gz -C release/install-root/
- name: Pull the Docker Image
- run: docker-compose pull focal-baremetal
- - name: arm-none-eabi-gcc-9_2
- run: QBS_AUTOTEST_PROFILE=arm-none-eabi-gcc-9_2 docker-compose run focal-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
+ run: docker-compose pull jammy-baremetal
+ - name: arm-none-eabi-gcc-10_3
+ run: QBS_AUTOTEST_PROFILE=arm-none-eabi-gcc-10_3 docker-compose run jammy-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
- name: avr-gcc-5_4
- run: QBS_AUTOTEST_PROFILE=avr-gcc-5_4 docker-compose run focal-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
+ run: QBS_AUTOTEST_PROFILE=avr-gcc-5_4 docker-compose run jammy-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
- name: msp430-gcc-4_6
- run: QBS_AUTOTEST_PROFILE=msp430-gcc-4_6 docker-compose run focal-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
- - name: xtensa-lx106-elf-gcc-9_2
- run: QBS_AUTOTEST_PROFILE=xtensa-lx106-elf-gcc-9_2 docker-compose run focal-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
- - name: riscv64-unknown-elf-gcc-9_3
- run: QBS_AUTOTEST_PROFILE=riscv64-unknown-elf-gcc-9_3 docker-compose run focal-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
- - name: sdcc-3_8_0-mcs51
- run: QBS_AUTOTEST_PROFILE=sdcc-3_8_0-mcs51 docker-compose run focal-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
- - name: sdcc-3_8_0-stm8
- run: QBS_AUTOTEST_PROFILE=sdcc-3_8_0-stm8 docker-compose run focal-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
- - name: sdcc-3_8_0-hcs8
- run: QBS_AUTOTEST_PROFILE=sdcc-3_8_0-hcs8 docker-compose run focal-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
+ run: QBS_AUTOTEST_PROFILE=msp430-gcc-4_6 docker-compose run jammy-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
+ - name: riscv64-unknown-elf-gcc-10_2
+ run: QBS_AUTOTEST_PROFILE=riscv64-unknown-elf-gcc-10_2 docker-compose run jammy-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
+ - name: sdcc-4_0_0-hcs8
+ run: QBS_AUTOTEST_PROFILE=sdcc-4_0_0-hcs8 docker-compose run jammy-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
+ - name: sdcc-4_0_0-mcs51
+ run: QBS_AUTOTEST_PROFILE=sdcc-4_0_0-mcs51 docker-compose run jammy-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
+ - name: sdcc-4_0_0-stm8
+ run: QBS_AUTOTEST_PROFILE=sdcc-4_0_0-stm8 docker-compose run jammy-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
+ - name: xtensa-lx106-elf-gcc-10_3
+ run: QBS_AUTOTEST_PROFILE=xtensa-lx106-elf-gcc-10_3 docker-compose run jammy-baremetal scripts/test-baremetal.sh release/install-root/usr/local/bin
test-baremetal-windows:
name: Run Baremetal tests (Windows)
@@ -465,7 +461,7 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Download artifact
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
name: qbs-windows-${{ github.run_id }}.zip
path: ./
@@ -611,76 +607,85 @@ jobs:
QTEST_FUNCTION_TIMEOUT: 9000000
QBS_AUTOTEST_PROFILE: 'qt'
QBS_TEST_SOURCE_ROOT: 'tests'
- QBS_EXTRA_GRPC_LIBS: 'absl_synchronization,grpc,gpr'
+ QBS_EXTRA_GRPC_LIBS: 'absl_cord,absl_cordz_handle,absl_cordz_info,absl_synchronization,grpc,gpr'
strategy:
fail-fast: false
matrix:
config:
- {
- name: 'Run macOS tests (Xcode 14.2)',
- runner: 'macos-12',
+ name: 'Run macOS tests (Xcode 15.1)',
+ runner: 'macos-13',
target: 'desktop',
toolchain: 'clang_64',
- xcodeVersion: '14.2',
- testProfile: 'xcode_14_2-macosx-x86_64',
+ xcodeVersion: '15.1',
+ testProfile: 'xcode_15_1-macosx-x86_64',
qtVersion: '6.5.0',
script: './scripts/test-qbs.sh',
}
- {
- name: 'Run macOS tests (Xcode 14.2, Qt 5.15)',
- runner: 'macos-12',
+ name: 'Run macOS tests (Xcode 15.1, Qt 5.15)',
+ runner: 'macos-13',
target: 'desktop',
toolchain: 'clang_64',
- xcodeVersion: '14.2',
- testProfile: 'xcode_14_2-macosx-x86_64',
+ xcodeVersion: '15.1',
+ testProfile: 'xcode_15_1-macosx-x86_64',
qtVersion: '5.15.2',
script: './scripts/test-qt.sh',
}
- {
- name: 'Run iOS tests (Xcode 14.2)',
- runner: 'macos-12',
+ name: 'Run iOS tests (Xcode 15.1)',
+ runner: 'macos-13',
target: 'ios',
toolchain: 'ios',
- xcodeVersion: '14.2',
- testProfile: 'xcode_14_1-iphoneos-arm64',
+ xcodeVersion: '15.1',
+ testProfile: 'xcode_15_1-iphoneos-arm64',
qtVersion: '6.5.0',
script: './scripts/test-qbs.sh',
}
- {
- name: 'Run iOS-sim tests (Xcode 14.2)',
- runner: 'macos-12',
+ name: 'Run iOS-sim tests (Xcode 15.1)',
+ runner: 'macos-13',
target: 'ios',
toolchain: 'ios',
- xcodeVersion: '14.2',
- testProfile: 'xcode_14_1-iphonesimulator-x86_64',
+ xcodeVersion: '15.1',
+ testProfile: 'xcode_15_1-iphonesimulator-x86_64',
qtVersion: '6.5.0',
script: './scripts/test-qbs.sh',
}
- {
- name: 'Run macOS tests (Xcode 13.4)',
- runner: 'macos-12',
+ name: 'Run macOS tests (Xcode 14.3.1)',
+ runner: 'macos-13',
target: 'desktop',
toolchain: 'clang_64',
- xcodeVersion: '13.4.1',
- testProfile: 'xcode_13_4_1-macosx-x86_64',
+ xcodeVersion: '14.3.1',
+ testProfile: 'xcode_14_3_1-macosx-x86_64',
+ qtVersion: '6.5.0',
+ script: './scripts/test-qbs.sh',
+ }
+ - {
+ name: 'Run macOS tests (Xcode 14.2)',
+ runner: 'macos-13',
+ target: 'desktop',
+ toolchain: 'clang_64',
+ xcodeVersion: '14.2',
+ testProfile: 'xcode_14_2-macosx-x86_64',
qtVersion: '6.5.0',
script: './scripts/test-qbs.sh',
}
-
- {
- name: 'Run macOS tests (Xcode 12.5)',
- runner: 'macos-11',
+ name: 'Run macOS tests (Xcode-less)',
+ runner: 'macos-13',
target: 'desktop',
toolchain: 'clang_64',
- xcodeVersion: '12.5.1',
- testProfile: 'xcode_12_5_1-macosx-x86_64',
+ xcodeVersion: '',
+ testProfile: 'xcode_13_4_1-macosx-x86_64',
qtVersion: '6.5.0',
script: './scripts/test-qbs.sh',
}
steps:
- uses: actions/checkout@v1
- name: Download artifact
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
name: qbs-macos-${{ github.run_id }}.tar.gz
path: ./
@@ -705,6 +710,7 @@ jobs:
version: ${{ matrix.config.qtVersion }}
- name: Select Xcode
run: sudo xcode-select --switch /Applications/Xcode_${{ matrix.config.xcodeVersion }}.app
+ if: matrix.config.xcodeVersion != ''
- name: Setup Qbs
run: |
qbs setup-toolchains --detect
@@ -712,6 +718,15 @@ jobs:
qbs config profiles.qt.baseProfile ${{ matrix.config.testProfile }}
qbs config defaultProfile qt
qbs config --list
+ if: matrix.config.xcodeVersion != ''
+ - name: Setup Qbs (Xcode-less)
+ run: |
+ qbs setup-toolchains /usr/bin/clang++ ${{ matrix.config.testProfile }}
+ qbs setup-qt $(which qmake) qt
+ qbs config profiles.qt.baseProfile ${{ matrix.config.testProfile }}
+ qbs config defaultProfile qt
+ qbs config --list
+ if: matrix.config.xcodeVersion == ''
- name: Run Tests
run: |
sudo chmod g+w /cores
@@ -773,7 +788,7 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Download artifact
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
name: qbs-windows-${{ github.run_id }}.zip
path: ./
@@ -876,7 +891,7 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Download artifact
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
name: qbs-windows-${{ github.run_id }}.zip
path: ./
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6b63097f6..1b65762e7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -37,35 +37,31 @@ jobs:
run: mkdir -p ~/.ccache
- name: test
run: echo ${{ github.ref }}
- - name: prepare timestamp
- id: get-timestamp
- run: echo ::set-output name=timestamp::$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- name: ccache cache files
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/.ccache
- key: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache-${{ steps.get-timestamp.outputs.timestamp }}
- restore-keys: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache-
- - name: Pull the Focal Image
- run: docker-compose pull focal
+ key: ${{ runner.os }}-${{ matrix.config.cacheid }}-ccache
+ - name: Pull the Docker Image
+ run: docker-compose pull jammy-qt6
- name: Print ccache stats
- run: docker-compose run focal ccache -s
+ run: docker-compose run jammy-qt6 ccache -s
- name: Build Qbs
- run: docker-compose run focal ${{ matrix.config.script }}
+ run: docker-compose run jammy-qt6 ${{ matrix.config.script }}
- name: Print ccache stats
- run: docker-compose run focal ccache -s
+ run: docker-compose run jammy-qt6 ccache -s
- name: Get archive name
id: get-archive-name
- run: echo ::set-output name=archive-name::$(git describe)
+ run: echo "archive-name=$(git describe)" >> $GITHUB_OUTPUT
- name: Upload artifacts
- uses: 'actions/upload-artifact@v2'
+ uses: 'actions/upload-artifact@v4'
with:
name: qbs-linux-${{ github.run_id }}.tar.gz
path: release/qbs-linux-${{ github.run_id }}.tar.gz
build-macos:
name: Build on macOS
- runs-on: macos-11
+ runs-on: macos-12
timeout-minutes: 60
env:
BUILD_OPTIONS: |
@@ -81,15 +77,11 @@ jobs:
- uses: actions/checkout@v1
- name: Create .ccache dir
run: mkdir -p ~/.ccache
- - name: prepare timestamp
- id: get-timestamp
- run: echo ::set-output name=timestamp::$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- name: ccache cache files
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/.ccache
- key: ${{ runner.os }}-release-ccache-${{ steps.get-timestamp.outputs.timestamp }}
- restore-keys: ${{ runner.os }}-release-ccache-
+ key: ${{ runner.os }}-release-ccache
- name: Install required packages
run: |
brew install ccache p7zip
@@ -104,7 +96,7 @@ jobs:
run: |
qbs setup-toolchains --detect
qbs setup-qt --detect
- qbs config profiles.qt.baseProfile xcode_11_7-macosx-x86_64
+ qbs config profiles.qt.baseProfile xcode_13_2_1-macosx-x86_64
qbs config defaultProfile qt
qbs config --list
- name: Print ccache stats
@@ -114,7 +106,7 @@ jobs:
- name: Print ccache stats
run: ccache -s
- name: Upload artifacts
- uses: 'actions/upload-artifact@v2'
+ uses: 'actions/upload-artifact@v4'
with:
name: qbs-macos-${{ github.run_id }}.tar.gz
path: release/qbs-macos-${{ github.run_id }}.tar.gz
@@ -132,16 +124,11 @@ jobs:
- name: Create .ccache dir
run: mkdir -p ~/.ccache
shell: bash
- - name: prepare timestamp
- id: get-timestamp
- run: echo ::set-output name=timestamp::$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- shell: bash
- name: clcache cache files
- uses: actions/cache@v2
+ uses: actions/cache@v4
with:
path: ~/.ccache
- key: ${{ runner.os }}-release-msvc-docker-clcache-${{ steps.get-timestamp.outputs.timestamp }}
- restore-keys: ${{ runner.os }}-release-msvc-docker-clcache-
+ key: ${{ runner.os }}-release-msvc-docker-clcache
- name: Pull the Windows Image
run: docker-compose pull windows
- name: Print clcache stats
@@ -162,10 +149,9 @@ jobs:
run: docker-compose run --rm windows clcache -s
- name: Get archive name
id: get-archive-name
- run: echo ::set-output name=archive-name::$(git describe)
- shell: bash
+ run: echo "archive-name=$(git describe)" >> $GITHUB_OUTPUT
- name: Upload artifacts
- uses: 'actions/upload-artifact@v2'
+ uses: 'actions/upload-artifact@v4'
with:
name: qbs-windows-${{ github.run_id }}
path: |
@@ -182,7 +168,7 @@ jobs:
submodules: true
- name: Get version name
id: get-version-name
- run: echo ::set-output name=version-name::$(cat VERSION)
+ run: echo "version-name=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Create directories
run: |
mkdir release
@@ -198,7 +184,7 @@ jobs:
cd tmp/
tar czf ../release/qbs-src-${{ steps.get-version-name.outputs.version-name }}.tar.gz qbs-src-${{ steps.get-version-name.outputs.version-name }}
- name: Download Linux artifacts
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
name: qbs-linux-${{ github.run_id }}.tar.gz
path: ./tmp
@@ -209,7 +195,7 @@ jobs:
cd tmp/
tar czf ../release/qbs-linux-x86_64-${{ steps.get-version-name.outputs.version-name }}.tar.gz qbs-linux-x86_64-${{ steps.get-version-name.outputs.version-name }}
- name: Download Windows artifacts
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
name: qbs-windows-${{ github.run_id }}
path: ./tmp/release
@@ -226,9 +212,9 @@ jobs:
find . -type f -printf '%f\n' | grep -v .txt | xargs sha256sum > sha256sums.txt
- name: Get archive name
id: get-archive-name
- run: echo ::set-output name=archive-name::$(git describe)
+ run: echo "archive-name=$(git describe)" >> $GITHUB_OUTPUT
- name: Upload artifacts
- uses: 'actions/upload-artifact@v2'
+ uses: 'actions/upload-artifact@v4'
with:
name: qbs-release-${{ steps.get-archive-name.outputs.archive-name }}
path: |
diff --git a/VERSION b/VERSION
index 7ec1d6db4..197c4d5c2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.1.0
+2.4.0
diff --git a/changelogs/changes-2.1.0.md b/changelogs/changes-2.1.0.md
new file mode 100644
index 000000000..bec55631b
--- /dev/null
+++ b/changelogs/changes-2.1.0.md
@@ -0,0 +1,28 @@
+# General
+* Improved speed and correctness of project resolving.
+* Fixed possible segmentation fault when quitting a session.
+* Fixed regression in BinaryFile (QBS-1740).
+* Added possibility to import and export Qbs settings in the JSON format (QBS-1685).
+
+# Modules
+* Dependencies are no longer merged by default in the qbspkgconfig module provider (QBS-1710).
+* Protobuf modules now export the desired c++ version (c++17 on macOS, c++14 otherwise).
+
+# Apple Support
+* Updated dmgbuild to the upstream.
+
+# Documentation
+* Added documentation for the path, filePath, product and project variables.
+* Added sample codesign settings to the Cocoa Touch Application example.
+
+# Build System
+* Updated Qt static Docker image to Qt 6.5.0 and Qbs 1.24.
+
+# Contributors
+* Andrey Filipenkov
+* Christian Kandeler
+* Denis Shienkov
+* Ivan Komissarov
+* Marc Mutz
+* Raphael Cotty
+* Thiemo van Engelen
diff --git a/changelogs/changes-2.1.1.md b/changelogs/changes-2.1.1.md
new file mode 100644
index 000000000..d0cdaad4f
--- /dev/null
+++ b/changelogs/changes-2.1.1.md
@@ -0,0 +1,8 @@
+# General
+* Fixed Probe lookup in multiplexed products.
+* Fixed excessively slow module merging in some circumstances.
+* Fixed building QuickJS on x86 systems.
+
+# Contributors
+* Christian Kandeler
+* Ivan Komissarov
diff --git a/changelogs/changes-2.1.2.md b/changelogs/changes-2.1.2.md
new file mode 100644
index 000000000..b83ecdbf5
--- /dev/null
+++ b/changelogs/changes-2.1.2.md
@@ -0,0 +1,19 @@
+# General
+* Fixed handling JS floating-point values for x86.
+* Fixed scope pollution and potential crash when assigning to provider properties (QBS-1747).
+* Fixed potential access to freed JSValues (QBS-1751).
+
+# Qt
+* Fixed building against Qt with "profiling" build variant (QBS-1758).
+
+# Apple
+* Fixed bundle module with Xcode-less profiles.
+* Fixed ApplicationExtension with Xcode-less profiles.
+
+# Infrastructure
+* Added CI job to be able to test XCode-less profiles on macOS.
+
+# Contributors
+* Christian Kandeler
+* Dmitry Shachnev
+* Ivan Komissarov
diff --git a/changelogs/changes-2.2.0.md b/changelogs/changes-2.2.0.md
new file mode 100644
index 000000000..ffa867f0a
--- /dev/null
+++ b/changelogs/changes-2.2.0.md
@@ -0,0 +1,36 @@
+# General
+* Improved speed of project resolving by employing multiple CPU cores, if available.
+* Improved speed of probes execution on macOS.
+* Object and array (var and varList) properties are now immutable in Probe items.
+
+# Language
+* Modules can now contain `Parameters` items.
+* ModuleProviders can now contain `PropertyOptions` items.
+
+# C/C++ support
+* Allow `"mold"` as value for `cpp.linkerVariant`.
+* The systemIncludePaths property is now handled correctly for clang-cl.
+
+# Apple support
+* Updated dmgbuild tool. This fixes bug that additional licenses are not shown in
+ the combobox in the resulting DMG image.
+
+# Qt support
+* Only create qbs modules for those Qt modules that products actually need.
+* Users can now opt out of using RPATH when linking on Linux.
+
+# Other
+* Protobuf module now requires pkg-config or built-in runtime.
+* Protobuf module now requires C++17 on all platforms.
+* Capnproto module: the outputDir property is now mutable.
+* Added support for Groups to the VisualStudio generator.
+* pkgconfig module provider: mergeDependencies property is deprecated.
+
+# Contributors
+* Christian Kandeler
+* Dmitrii Meshkov
+* Ivan Komissarov
+* Nick Karg
+* Serhii Olendarenko
+* Thiemo van Engelen
+* Thorbjørn Lindeijer
diff --git a/changelogs/changes-2.2.1.md b/changelogs/changes-2.2.1.md
new file mode 100644
index 000000000..323da791a
--- /dev/null
+++ b/changelogs/changes-2.2.1.md
@@ -0,0 +1,12 @@
+# Language
+* Fixed JavaScript Date() constructor on Windows (QBS-1768).
+
+# C/C++ support
+* Worked around crash in cl.exe when retrieving built-in defines (QBS-1743).
+
+# CI
+* Fixed building release packages.
+
+# Contributors
+* Christian Kandeler
+* Ivan Komissarov
diff --git a/changelogs/changes-2.2.2.md b/changelogs/changes-2.2.2.md
new file mode 100644
index 000000000..2f82381ea
--- /dev/null
+++ b/changelogs/changes-2.2.2.md
@@ -0,0 +1,18 @@
+# General
+* Fixed name collision check for multi-part modules (QBS-1772).
+* Fixed potential assertion when attaching properties on non-present modules (QBS-1776).
+
+# C/C++
+* Fixed handling assember flags with MSVC (QBS-1774).
+
+# Qt
+* Fixed the qbspkgconfig Qt provider for the case when there is no Qt (QBS-1777).
+
+# Other
+* Make protobuf usable without qbspkgconfig again (QBS-1663).
+* Add support for the definePrefix option to qbspkgconfig.
+
+# Contributors
+* Björn Schäpers
+* Christian Kandeler
+* Ivan Komissarov
diff --git a/changelogs/changes-2.3.0.md b/changelogs/changes-2.3.0.md
new file mode 100644
index 000000000..9062b27ff
--- /dev/null
+++ b/changelogs/changes-2.3.0.md
@@ -0,0 +1,37 @@
+# General
+* Added an LSP language server that provides support for following symbols and completion
+ in IDEs (QBS-395).
+* Module properties are now directly available within groups in modules (QBS-1770).
+* Added possibility to export products to CMake via the new Exporter.cmake module.
+* Deprecated the pkgconfig-based fallback module provider.
+* If a project needs to be re-resolved, we now print the reason.
+* Added some tutorials.
+* Wildards handling was rewritten to track changes more accurate.
+* Module 'validate' scripts are no longer run for erroneous product in IDE mode.
+* Add example how to use Exporters.
+
+# C/C++ Support
+* Private dependencies of products are not traversed more than once anymore (QBS-1714).
+
+# Language
+* Module properties are now accessible for groups in modules (QBS-1770).
+* Fixed pathList properties in Probes (QBS-1785).
+* The qbspkgconfig.mergeDependencies property was removed.
+* ModuleProviders now support the 'allowedValues' property of the PropertyDeclaration item
+ (QBS-1748).
+
+# Apple
+* Adapted darwin support to Xcode 15.3.
+
+# CI
+* Changed Linux Docker images from Focal to Jammy.
+* Updated compilers and linters to recent versions.
+* Added clang-format job to check code style.
+* The project.withExamples property was removed.
+
+# Contributors
+* Christian Kandeler
+* Dmitrii Meshkov
+* Ivan Komissarov
+* Raphael Cotty
+* Richard Weickelt
diff --git a/changelogs/changes-2.3.1.md b/changelogs/changes-2.3.1.md
new file mode 100644
index 000000000..6c4b6c0ad
--- /dev/null
+++ b/changelogs/changes-2.3.1.md
@@ -0,0 +1,17 @@
+# Language
+* Fixed look-up of qbs properties in module providers via probes (QBS-1742).
+
+# Apple support
+* Fixed codesing module when multiplexing over build variants (QBS-1775).
+
+# Qt support
+* Fixed retrieving minimum macOS/iOS versions for Qt 6.7.1.
+
+# CI
+* Fixed release jobs.
+* Fixed macOS 14.2 job.
+* Updated GitHub actions to recent versions to avoid warnings.
+
+# Contributors
+* Christian Kandeler
+* Ivan Komissarov
diff --git a/doc/appendix/json-api.qdoc b/doc/appendix/json-api.qdoc
index b555825f9..7b093bc4e 100644
--- a/doc/appendix/json-api.qdoc
+++ b/doc/appendix/json-api.qdoc
@@ -94,6 +94,7 @@
\header \li Property \li Type
\row \li api-level \li int
\row \li api-compat-level \li int
+ \row \li lsp-socket \li string
\endtable
The value of \c api-level is increased whenever the API is extended, for instance
@@ -108,6 +109,10 @@
The value of \c api-compat-level is always less than or equal to the
value of \c api-level.
+ The value of \c lsp-socket is a path to a local domain socket (on Unix) or
+ a named pipe (on Windows). It provides a server implementing the
+ \l{https://microsoft.github.io/language-server-protocol}{Language Server Protocol}.
+
\section1 Resolving a Project
To instruct \QBS to load a project from disk, a request of type
@@ -121,10 +126,10 @@
\row \li dry-run \li bool \li no
\row \li environment \li \l Environment \li no
\row \li error-handling-mode \li string \li no
- \row \li fallback-provider-enabled \li bool \li no
\row \li force-probe-execution \li bool \li no
\row \li log-time \li bool \li no
\row \li log-level \li \l LogLevel \li no
+ \row \li max-job-count \li int \li no
\row \li module-properties \li list of strings \li no
\row \li overridden-properties \li object \li no
\row \li project-file-path \li FilePath \li if resolving from scratch
diff --git a/doc/config/macros.qdocconf b/doc/config/macros.qdocconf
index 6811215c0..31b9b9a8d 100644
--- a/doc/config/macros.qdocconf
+++ b/doc/config/macros.qdocconf
@@ -51,6 +51,8 @@ macro.endfloat.HTML = "</div>"
macro.clearfloat.HTML = "<br style=\"clear: both\" />"
macro.emptyspan.HTML = "<span></span>"
+macro.CMAKE = "CMake"
+
# Embed YouTube content by video ID - Example: \youtube dQw4w9WgXcQ
# Also requires a <ID>.jpg thumbnail for offline docs. In .qdocconf, add:
#
diff --git a/doc/doc.qbs b/doc/doc.qbs
index 03c82e18c..0dfc5f474 100644
--- a/doc/doc.qbs
+++ b/doc/doc.qbs
@@ -30,6 +30,7 @@ Project {
"fixnavi.pl",
"howtos.qdoc",
"qbs.qdoc",
+ "tutorial.qdoc",
"qbs-online.qdocconf",
"config/*.qdocconf",
"config/style/qt5-sidebar.html",
diff --git a/doc/howtos.qdoc b/doc/howtos.qdoc
index 268bee7c0..9a81318ee 100644
--- a/doc/howtos.qdoc
+++ b/doc/howtos.qdoc
@@ -25,7 +25,7 @@
**
****************************************************************************/
/*!
- \previouspage custom-modules.html
+ \previouspage tutorial-8.html
\nextpage reference.html
\page howtos.html
@@ -467,15 +467,17 @@
\section1 How do I build against libraries that provide pkg-config files?
- Just add a \l Depends item that matches the name of the pkg-config module, and \QBS
- will automatically employ \l{https://www.freedesktop.org/wiki/Software/pkg-config}{pkg-config}
+ Just add a \l Depends item that matches the name of the pkg-config module,
+ set the \l Product::qbsModuleProviders property to \c "qbspkgconfig",
+ and \QBS will employ
+ \l{https://www.freedesktop.org/wiki/Software/pkg-config}{pkg-config}
to find the headers and libraries if no matching \QBS module can be found. For instance,
to build against the OpenSSL library, you would write this:
\code
+ qbsModuleProviders: "qbspkgconfig"
Depends { name: "openssl" }
\endcode
- That's it. The pkg-config behavior can be fine-tuned via the \l pkgconfig module,
- but normally you will not need to pull it in explicitly.
+ That's it. The pkg-config behavior can be fine-tuned via the \l qbspkgconfig provider.
Internally, this functionality is implemented via \l {Module Providers}
diff --git a/doc/qbs.qdoc b/doc/qbs.qdoc
index c77dae824..cbe33f98a 100644
--- a/doc/qbs.qdoc
+++ b/doc/qbs.qdoc
@@ -70,6 +70,18 @@
\li \l{Special Property Values}
\li \l{Module Providers}
\endlist
+ \li \l{Tutorial}
+ \list
+ \li \l{tutorial-1.html}{Application}
+ \li \l{tutorial-2.html}{Static Library}
+ \li \l{tutorial-3.html}{Dynamic Library}
+ \li \l{tutorial-4.html}{Convenience Items}
+ \li \l{tutorial-5.html}{Autotest}
+ \li \l{tutorial-6.html}{Project Properties}
+ \li \l{tutorial-7.html}{Buildconfig Module}
+ \li \l{tutorial-8.html}{Configurable Library}
+ \li \l{tutorial-9.html}{Version Header}
+ \endlist
\li \l{How-tos}
\li \l{Reference}
\list
@@ -462,7 +474,7 @@
To build \QBS from the source, you need:
\list
- \li Qt 5.14, or later
+ \li Qt 5.15, or later
\li Windows: MinGW with GCC 4.9 or Microsoft Visual Studio 2015,
or later
\li Linux: GCC 4.9, or later, or Clang 3.9.0, or later
@@ -1068,7 +1080,6 @@
*/
-
/*!
\previouspage usage.html
\page language-introduction.html
@@ -1881,7 +1892,7 @@
/*!
\previouspage special-property-values.html
\page module-providers.html
- \nextpage howtos.html
+ \nextpage tutorial.html
\title Module Providers
@@ -1916,12 +1927,6 @@
a second attempt will be made to locate the dependency using the new paths.
The search for a matching module provider ends as soon as one was found, regardless
of whether it created any modules or not.
- \li If no matching module provider was found in any of the search paths, \QBS will fall back
- to a generic module provider, which creates a module that attempts to locate the
- dependency via \c pkg-config.
- This fallback mechanism can be disabled in the respective
- \l{Depends::enableFallback}{Depends} item or globally via the
- \l{no-fallback-module-provider}{--no-fallback-module-provider} option.
\endlist
\section1 Selecting Module Providers
diff --git a/doc/reference/cli/builtin/cli-build.qdoc b/doc/reference/cli/builtin/cli-build.qdoc
index 683b1ebb8..8547a78d2 100644
--- a/doc/reference/cli/builtin/cli-build.qdoc
+++ b/doc/reference/cli/builtin/cli-build.qdoc
@@ -78,8 +78,6 @@
\include cli-options.qdocinc products-specified
\include cli-options.qdocinc settings-dir
\include cli-options.qdocinc show-progress
- \target no-fallback-module-provider
- \include cli-options.qdocinc no-fallback-module-provider
\include cli-options.qdocinc wait-lock
\section1 Parameters
diff --git a/doc/reference/cli/builtin/cli-resolve.qdoc b/doc/reference/cli/builtin/cli-resolve.qdoc
index 4569980bd..99f1658ea 100644
--- a/doc/reference/cli/builtin/cli-resolve.qdoc
+++ b/doc/reference/cli/builtin/cli-resolve.qdoc
@@ -55,7 +55,6 @@
\include cli-options.qdocinc more-verbose
\include cli-options.qdocinc settings-dir
\include cli-options.qdocinc show-progress
- \include cli-options.qdocinc no-fallback-module-provider
\include cli-options.qdocinc deprecation-warnings
\section1 Parameters
diff --git a/doc/reference/items/convenience/appleapplicationdiskimage.qdoc b/doc/reference/items/convenience/appleapplicationdiskimage.qdoc
index 8db7bc4db..d1fa15052 100644
--- a/doc/reference/items/convenience/appleapplicationdiskimage.qdoc
+++ b/doc/reference/items/convenience/appleapplicationdiskimage.qdoc
@@ -77,10 +77,11 @@
The base directory from which artifacts installed into the disk image will be copied.
This directory is always considered to be relative to \l{qbs::installRoot}
- {qbs.installRoot}.
+ {qbs.installRoot}/\l{qbs::installPrefix}
+ {qbs.installPrefix}.
For example, if the application Example.app exists at
- \c{qbs.installRoot/Applications/Example.app}, and the value of this property
- is \c{"/Applications"}, the application will be located at\c{/Example.app}
+ \c{qbs.installRoot/qbs.installPrefix/Applications/Example.app}, and the value of this property
+ is \c{"/Applications"}, the application will be located at \c{/Example.app}
relative to the disk image root. Therefore, its full path when the disk
image is mounted would be something like \c{/Volumes/Example-1.0/Example.app}.
diff --git a/doc/reference/items/language/depends.qdoc b/doc/reference/items/language/depends.qdoc
index 7cc270483..937828b8c 100644
--- a/doc/reference/items/language/depends.qdoc
+++ b/doc/reference/items/language/depends.qdoc
@@ -197,12 +197,3 @@
\nodefaultvalue
*/
-
-/*!
- \qmlproperty bool Depends::enableFallback
-
- Whether to fall back to a pkg-config based \l{Module Providers}{module provider}
- if the dependency is not found.
-
- \defaultvalue \c true
-*/
diff --git a/doc/reference/items/language/group.qdoc b/doc/reference/items/language/group.qdoc
index c9b6dd1cf..fa6784ca6 100644
--- a/doc/reference/items/language/group.qdoc
+++ b/doc/reference/items/language/group.qdoc
@@ -74,10 +74,13 @@
}
}
\endcode
- When specifying files, you can use the wildcards "*", "?" and "[]", which have their usual meaning.
- By default, matching files are only picked up directly from the parent directory, but you can tell \QBS to
- consider the whole directory tree. It is also possible to exclude certain files from the list.
- The pattern ** used in a pathname expansion context will match all files and zero or more
+ When specifying files, you can use the wildcards \c "*", \c "?" and \c "[]", which
+ have their
+ \l{https://en.wikipedia.org/wiki/Wildcard_character#File_and_directory_patterns}{usual meaning}
+ as in Unix Shell. By default, matching files are only picked up directly from the
+ parent directory, but you can tell \QBS to consider the whole directory tree.
+ It is also possible to exclude certain files from the list.
+ The pattern \c "**" used in a pathname expansion context will match all files and zero or more
directories and subdirectories.
For example:
\snippet reference/items/language/group.qbs 0
@@ -114,13 +117,15 @@
*/
/*!
- \qmlproperty list Group::files
+ \qmlproperty pathList Group::files
The files in the group. Mutually exclusive with \l{fileTagsFilter}.
Relative paths are resolved using the parent directory of the file
that contains the Group item. However, if the \l{prefix} property is set to
an absolute path, then that one becomes the base directory.
+ The values can contain wildcards.
+
\defaultvalue An empty list
*/
@@ -146,7 +151,7 @@
*/
/*!
- \qmlproperty list Group::fileTagsFilter
+ \qmlproperty stringList Group::fileTagsFilter
List of \l{Artifact::fileTags}{artifact.fileTags} to match. Any properties
set in this group will be applied to the product's artifacts whose file tags
@@ -170,7 +175,7 @@
*/
/*!
- \qmlproperty list Group::fileTags
+ \qmlproperty stringList Group::fileTags
A list of file tags to attach to the group's files. These can then be
matched by a \l{Rule}{rule}.
@@ -196,10 +201,13 @@
*/
/*!
- \qmlproperty list Group::excludeFiles
+ \qmlproperty pathList Group::excludeFiles
+
+ A list of files that are \e subtracted from the \l{files} list.
- A list of files that are \e subtracted from the files list. Useful when
- using wildcards.
+ The values can contain wildcards.
+
+ This property is ignored if \l{fileTagsFilter} is set.
\defaultvalue An empty list
*/
diff --git a/doc/reference/items/language/moduleprovider.qdoc b/doc/reference/items/language/moduleprovider.qdoc
index 81a09a66b..09cabb39b 100644
--- a/doc/reference/items/language/moduleprovider.qdoc
+++ b/doc/reference/items/language/moduleprovider.qdoc
@@ -67,6 +67,19 @@
*/
/*!
+ \qmlproperty bool ModuleProvider::isEager
+
+ Holds whether provider is eager.
+
+ Eager provider is executed only once and should create multiple modules at once when executed).
+ A non-eager provider is executed multiple times, one time for each module \QBS tries to
+ instantiate.
+
+ \sa ModuleProvider::moduleName
+ \default true
+*/
+
+/*!
\qmlproperty string ModuleProvider::name
The name of the module provider.
@@ -83,6 +96,17 @@
*/
/*!
+ \qmlproperty string ModuleProvider::moduleName
+
+ This property is set by QBS for non-eager providers and contains the name of the module
+ that is currently being instantiated by the provider.
+
+ For eager providers, the value of this property is \c undefined.
+
+ \sa ModuleProvider::isEager
+*/
+
+/*!
\qmlproperty string ModuleProvider::outputBaseDir
The path under which the new modules should be created when \l relativeSearchPaths
diff --git a/doc/reference/items/language/parameters.qdoc b/doc/reference/items/language/parameters.qdoc
index 325f1680e..fb7653528 100644
--- a/doc/reference/items/language/parameters.qdoc
+++ b/doc/reference/items/language/parameters.qdoc
@@ -32,10 +32,10 @@
\ingroup list-of-items
\keyword QML.Parameters
- \brief Defines default values for dependency parameters within Export items.
+ \brief Defines default values for dependency parameters within Module and Export items.
The Parameters item defines default values for dependency parameters within
- \l{Export} items.
+ \l{Module} and \l{Export} items.
Example:
\code
diff --git a/doc/reference/items/probe/binary-probe.qdoc b/doc/reference/items/probe/binary-probe.qdoc
index 7c8cd2f42..919d0b89e 100644
--- a/doc/reference/items/probe/binary-probe.qdoc
+++ b/doc/reference/items/probe/binary-probe.qdoc
@@ -50,6 +50,7 @@
For example, BinaryProbe can be used to search for a protobuf compiler executable as follows:
\code
+ // Assuming module is called "myproto"
import qbs.File
import qbs.Probes
@@ -70,10 +71,10 @@
Rule {
// rule input/outputs here...
- // run executable
+ // run executable for the module called "myproto":
prepare: {
var args = // initialize arguments...
- var cmd = new Command(executableFilePath, args);
+ var cmd = new Command(input.myproto.executableFilePath, args);
cmd.highlight = "codegen";
cmd.description = "generating protobuf files for " + input.fileName;
return [cmd];
diff --git a/doc/reference/items/probe/pkgconfig-probe.qdoc b/doc/reference/items/probe/pkgconfig-probe.qdoc
index 22fd510ac..66e008601 100644
--- a/doc/reference/items/probe/pkgconfig-probe.qdoc
+++ b/doc/reference/items/probe/pkgconfig-probe.qdoc
@@ -51,8 +51,12 @@
maxVersion: '5.99.99'
}
files: 'main.cpp'
- cpp.cxxFlags: pkgConfig.cflags
- cpp.linkerFlags: pkgConfig.libs
+ cpp.defines : pkgConfig.defines
+ cpp.includePaths: pkgConfig.includePaths
+ cpp.dynamicLibraries: pkgConfig.libraries
+ cpp.libraryPaths: pkgConfig.libraryPaths
+ cpp.commonCompilerFlags: pkgConfig.compilerFlags
+ cpp.linkerFlags: pkgConfig.linkerFlags
}
\endcode
*/
@@ -194,9 +198,6 @@
This output property contains the list of library paths that should be passed to a linker when
using requested package.
- This property sets the value of the PKG_CONFIG_LIBDIR environment variable passed to
- the \c pkg-config binary.
-
\nodefaultvalue
*/
diff --git a/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc b/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc
index f9c4e9ade..a7f6fe6b7 100644
--- a/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc
+++ b/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc
@@ -87,6 +87,29 @@
*/
/*!
+ \qmlproperty bool qbspkgconfig::definePrefix
+
+ If this property is \c true, then \QBS will override the ${prefix} variable in the packages
+ with a value that is guessed based on the location of the .pc file.
+
+ This option corresponds to the \c --define-prefix / \c --dont-define-prefix command line
+ options of the \c pkg-config tool.
+
+ \defaultvalue \c true on Windows, \c false otherwise
+*/
+
+/*!
+ \qmlproperty stringList qbspkgconfig::executableNames
+
+ The names of the \c pkg-config executable to search for.
+
+ Note that since newer distributions use \l{http://pkgconf.org}{pkgconf} by default, it has
+ higher priority over \c pkg-config.
+
+ \defaultvalue \c{["pkgconf", "pkg-config"]}
+*/
+
+/*!
\qmlproperty path qbspkgconfig::sysroot
Set this property if you need to overwrite the default search sysroot path used by
@@ -100,7 +123,7 @@
Setting this property to \c undefined or empty (\c "") value will use pkg-config's default
search paths:
\code
- qbs build module-providers.pkgconfig.sysroot:undefined
+ qbs resolve moduleProviders.qbspkgconfig.sysroot:undefined
\endcode
This property is the equivalent of the \c{PKG_CONFIG_SYSROOT_DIR} variable for the
@@ -108,15 +131,3 @@
\defaultvalue \c "" on macOS, \c qbs.sysroot on other platforms
*/
-
-/*!
- \qmlproperty bool qbspkgconfig::mergeDependencies
-
- Holds whether dependencies should be merged by pkg-config or \QBS.
-
- If set to true, dependencies are merged by pkg-config meaning each generated module
- is self-contained and does not depend on other modules. If set to false, generated modules
- may depend on other modules and property merging is done by \QBS.
-
- \defaultvalue \c false
-*/
diff --git a/doc/reference/modules/capnprotocpp-module.qdoc b/doc/reference/modules/capnprotocpp-module.qdoc
index c3c8660c9..7635edd9e 100644
--- a/doc/reference/modules/capnprotocpp-module.qdoc
+++ b/doc/reference/modules/capnprotocpp-module.qdoc
@@ -104,12 +104,10 @@
/*!
\qmlproperty string capnproto.cpp::outputDir
- \readonly
The directory where the \c capnpc compiler generated files are placed.
- The value of this property is automatically set by \QBS and cannot be
- changed by the user.
+ \defaultvalue \c product.buildDirectory + "/capnp"
*/
/*!
diff --git a/doc/reference/modules/cpp-module.qdoc b/doc/reference/modules/cpp-module.qdoc
index f0b6de063..2b344aac2 100644
--- a/doc/reference/modules/cpp-module.qdoc
+++ b/doc/reference/modules/cpp-module.qdoc
@@ -80,6 +80,7 @@
May contain the values: "weak", "lazy", "reexport", and "upward"; refer to the Apple
ld64 man page for full details. \c{undefined} uses normal linking.
Currently only applies when linking for Apple platforms.
+ Note that \c "lazy" mode is deprecated and doesn't work with Xcode 15 and above.
\endtable
\section2 Relevant File Tags
@@ -1402,8 +1403,8 @@
will result in the \c {-fuse-ld} option being emitted when linking with \c gcc,
\c clang or \c clang-cl. Other toolchains do not support this property.
- The possible values for \c clang and \c gcc are \c "bfd", \c "gold" and \c "lld",
- the possible values for \c clang-cl are \c "link" and \c "lld".
+ The possible values for \c clang and \c gcc are \c "bfd", \c "gold", \c "lld"
+ and \c "mold", the possible values for \c clang-cl are \c "link" and \c "lld".
The following example demonstrates how to change the linker for different toolchains:
diff --git a/doc/reference/modules/exporter-cmake.qdoc b/doc/reference/modules/exporter-cmake.qdoc
new file mode 100644
index 000000000..2f4191a76
--- /dev/null
+++ b/doc/reference/modules/exporter-cmake.qdoc
@@ -0,0 +1,135 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Raphael 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$
+**
+****************************************************************************/
+
+/*!
+ \qmltype Exporter.cmake
+ \inqmlmodule QbsModules
+ \since Qbs 2.3
+
+ \brief Provides support for generating \CMAKE packages from dynamic, static and header library
+ products.
+
+ The Exporter.cmake module contains the properties and rules to create a \CMAKE config
+ \l{https://cmake.org/cmake/help/v3.29/manual/cmake-packages.7.html#config-file-packages}{files}
+ from the \l Export item of a \l Product.
+
+ For instance, suppose you are creating a library. To allow exporting to \CMAKE, you would write
+ something like the following:
+ \code
+ DynamicLibrary {
+ name: "mylibrary"
+ qbs.installPrefix: "/opt/mylibrary"
+ Depends { name: "Exporter.cmake" }
+ Exporter.cmake.packageName: "MyLibrary"
+ property string headersInstallDir: "include"
+ // ...
+ Group {
+ name: "API headers"
+ files: ["mylib.h"]
+ qbs.install: true
+ qbs.installDir: headersInstallDir
+ }
+ Group {
+ fileTagsFilter: ["Exporter.cmake.package"]
+ qbs.installDir: "lib/cmake/MyLibrary"
+ }
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: FileInfo.joinPaths(
+ exportingProduct.qbs.installRoot,
+ exportingProduct.qbs.installPrefix,
+ exportingProduct.headersInstallDir)
+ }
+ }
+ \endcode
+ To build against this library, from within your \CMAKE project, you simply
+ use \l{https://cmake.org/cmake/help/v3.29/command/find_package.html}{find_package}:
+ \code
+ find_package(MyLibrary PATHS REQUIRED)
+ add_executable(Consumer main.cpp)
+ target_link_libraries(Consumer MyLibrary)
+ \endcode
+
+ \section2 Relevant File Tags
+ \target filetags-exporter-qbs
+
+ \table
+ \header
+ \li Tag
+ \li Since
+ \li Description
+ \row
+ \li \c{"Exporter.cmake.package"}
+ \li 2.3.0
+ \li This tag is attached to all generated module files.
+ \row
+ \li \c{"Exporter.cmake.configFile"}
+ \li 2.3.0
+ \li This tag is attached to the generated config file.
+ \row
+ \li \c{"Exporter.cmake.versionFile"}
+ \li 2.3.0
+ \li This tag is attached to the generated version file.
+ \endtable
+*/
+
+/*!
+ \qmlproperty string Exporter.cmake::configFileName
+
+ The name of the generated config file.
+
+ \defaultvalue \c{packageName + "Config.cmake"}
+*/
+
+/*!
+ \qmlproperty string Exporter.cmake::versionFileName
+
+ The name of the generated version file.
+
+ \defaultvalue \c{packageName + "ConfigVersion.cmake"}
+*/
+
+/*!
+ \qmlproperty string Exporter.cmake::packageName
+
+ The name of the \CMAKE package.
+
+ \defaultvalue \l{Product::targetName}{Product.targetName}
+*/
+
diff --git a/doc/reference/modules/protobufcpp-module.qdoc b/doc/reference/modules/protobufcpp-module.qdoc
index 85851c4ae..2c96eaeef 100644
--- a/doc/reference/modules/protobufcpp-module.qdoc
+++ b/doc/reference/modules/protobufcpp-module.qdoc
@@ -78,6 +78,13 @@
\li 1.18.0
\li This tag is attached to the header files generated by \c protoc compiler.
\endtable
+
+ \section2 Dependencies
+
+ The \l protobuf.cpp module requires runtime libraries to be operational. It depends on the
+ \c "protobuflib" module which can be created by the \l qbspkgconfig or fallback module
+ providers (the corresponding packages are \c protobuf or \c protobuf-lite). If \l useGrpc is
+ set to true, the \l protobuf.cpp module also depends on the \c "grpc++" module.
*/
/*!
@@ -101,24 +108,6 @@
*/
/*!
- \qmlproperty string protobuf.cpp::grpcIncludePath
-
- The path where grpc++ headers are located. Set this property to override the
- default location.
-
- \defaultvalue \c auto-detected
-*/
-
-/*!
- \qmlproperty string protobuf.cpp::grpcLibraryPath
-
- The path where the grpc++ library is located. Set this property to override the
- default location.
-
- \defaultvalue \c auto-detected
-*/
-
-/*!
\qmlproperty pathList protobuf.cpp::importPaths
The list of imports that are passed to the \c protoc tool via the \c --proto_path option.
@@ -131,24 +120,6 @@
*/
/*!
- \qmlproperty string protobuf.cpp::includePath
-
- The path where protobuf C++ headers are located. Set this property to override the
- default location.
-
- \defaultvalue \c auto-detected
-*/
-
-/*!
- \qmlproperty string protobuf.cpp::libraryPath
-
- The path where the protobuf C++ library is located. Set this property to override the
- default location.
-
- \defaultvalue \c auto-detected
-*/
-
-/*!
\qmlproperty string protobuf.cpp::outputDir
\readonly
diff --git a/doc/reference/modules/qt-core-module.qdoc b/doc/reference/modules/qt-core-module.qdoc
index afc4a0f11..6aedf2a69 100644
--- a/doc/reference/modules/qt-core-module.qdoc
+++ b/doc/reference/modules/qt-core-module.qdoc
@@ -211,6 +211,32 @@
*/
/*!
+ \qmlproperty string Qt.core::generatedHeadersDir
+
+ The directory where tools that generate headers (such as \c moc or \c uic) put resulting
+ files.
+
+ Normally, you don't need to change this property. The one use-case is when there are several
+ files with the same file name in the Product. The file name produced by \c moc is based
+ only on the source file's base file name (without the directory path) which leads to a conflict
+ in the mentioned case. You can resolve the conflict by setting this property to a non-default
+ value for one of the files. For example:
+
+ \code
+ QtApplication {
+ files: ["my_cool_header.h", "main.cpp"]
+ Group {
+ name: "legacy"
+ files: "legacy/my_cool_header.h"
+ Qt.core.generatedHeadersDir: "qt.legacy.headers"
+ }
+ }
+ \endcode
+
+ \defaultvalue \c product.buildDirectory + "/qt.headers"
+*/
+
+/*!
\qmlproperty bool Qt.core::metaTypesInstallDir
The directory to install the metatypes file into. If this property is empty or undefined,
@@ -352,6 +378,14 @@
*/
/*!
+ \qmlproperty string Qt.core::qdocOutputDir
+
+ The directory name where the \c qdoc tool writes its output.
+
+ \defaultvalue \c{product.buildDirectory + "/qdoc_html"}
+*/
+
+/*!
\qmlproperty string Qt.core::qmBaseName
The base name of the \c .qm file to be built from the \c .ts files in the
@@ -363,6 +397,14 @@
*/
/*!
+ \qmlproperty string Qt.core::qmDir
+
+ The directory name where to put the \c .qm file to be built.
+
+ \defaultvalue \l{Product::destinationDirectory}{product.destinationDirectory}
+*/
+
+/*!
\qmlproperty string Qt.core::qtBuildVariant
Specifies the type of Qt libraries to build against: \c "debug" or
@@ -468,3 +510,11 @@
\defaultvalue \c{versionParts[2]}
*/
+
+/*!
+ \qmlproperty bool Qt.core::useRPaths
+
+ Whether to add \l{Qt.core::libPath}{Qt.core.libPath} to \l{cpp::rpaths}{cpp.rpaths}.
+
+ \defaultvalue \c true on Linux, \c false everywhere else.
+*/
diff --git a/doc/tutorial.qdoc b/doc/tutorial.qdoc
new file mode 100644
index 000000000..7d336b5b3
--- /dev/null
+++ b/doc/tutorial.qdoc
@@ -0,0 +1,647 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \previouspage module-providers.html
+ \page tutorial.html
+ \nextpage tutorial-1.html
+
+ \title Tutorial
+
+ The tutorial describes the process of creating a typical \QBS project and explains core
+ concepts.
+
+ \list
+ \li \l{tutorial-1.html}{Application}
+ \li \l{tutorial-2.html}{Static Library}
+ \li \l{tutorial-3.html}{Dynamic Library}
+ \li \l{tutorial-4.html}{Convenience Items}
+ \li \l{tutorial-5.html}{Autotest}
+ \li \l{tutorial-6.html}{Project Properties}
+ \li \l{tutorial-7.html}{Buildconfig Module}
+ \li \l{tutorial-8.html}{Configurable Library}
+ \li \l{tutorial-9.html}{Version Header}
+ \endlist
+*/
+
+/*!
+ \previouspage tutorial.html
+ \page tutorial-1.html
+ \nextpage tutorial-2.html
+
+ \title Application
+
+ Let's start with a mandatory Hello World example. There is a Hello World example in the
+ \l{overview.html#well-defined-language}{overview section}, but this time we will create a
+ real-world project.
+
+ We will start with a simple \QBS project file:
+ \snippet ../tutorial/chapter-1/myproject.qbs 0
+
+ Here, we set the \l{Project::name}{name} of the project to \c "My Project" - it is mainly
+ used for IDEs but can also be used to reference a project when setting properties from
+ command-line. We also set the \l{Project::minimumQbsVersion}{minimumQbsVersion} - this property
+ can be useful if the project depends on features that are not present in earlier \QBS
+ versions.
+
+ The \l{Project::references}{references} property contains the path to a file
+ that describes our application. This file is located in a separate \c app directory -
+ typically, projects tend to have quite a complicated structure but \QBS does not enforce any
+ specific layout, so we will simply put each product in a separate directory.
+
+ The application file sets several properties:
+ \snippet ../tutorial/chapter-1/app/app.qbs 0
+
+ The \l{Product::name}{name} property identifies the product.
+ The \l{Product::targetName}{targetName} property sets the name of the resulting binary
+ (without the \c .exe extension on Windows, which is appended automatically). By default,
+ \l{Product::targetName}{targetName} defaults to \l{Product::name}{name}. The
+ \l{Product::files}{files} property contains a single \c main.c file which is a trivial
+ \e{hello world} application:
+ \snippet ../tutorial/chapter-1/app/main.c 0
+
+ We set \l{Product::consoleApplication}{consoleApplication} to \c true to indicate that our
+ application is going to be used from a terminal. For example, on Windows, this will spawn a
+ new console window if you double-click the resulting binary, while on macOS it will create a
+ standalone binary file instead of an
+ \l{https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html}{application bundle}.
+
+ By default, the \l{Application::install}{CppApplication.install} property is \c false and thus
+ \QBS does not install the binary. If the \l{Application::install}{install} property is
+ \c true, when building a project, \QBS will also install it into an \e{installation root}
+ folder which by default is named \c install-root and located under the build directory. This
+ folder will contain only resulting artifacts without the build leftovers and files will have
+ the same layout as in the target system. The \l{Application::install}{install} property should
+ be set to \c true for all products that are to be distributed.
+ See the \l{installing-files.html}{Installing Files} section for more details.
+
+ We can now build and run our application from the project directory:
+ \code
+ chapter-1 $ qbs build
+ ...
+ Building for configuration default
+ compiling main.c [My Application]
+ ...
+ linking myapp [My Application]
+ ...
+ Build done for configuration default.
+
+ chapter-1 $ qbs run
+ ...
+ Starting target. Full command line: .../default/install-root/usr/local/bin/myapp
+ Hello, world
+ \endcode
+
+ The \QBS output to console and default installation location may vary between different
+ platforms.
+
+ In most cases, \QBS should be able to find the default compiler (for example, GCC or
+ Clang on Linux, Visual Studio on Windows, or Xcode on macOS), but if that's not the
+ case, see the \l{configuring.html}{configuring} section.
+
+ In the following chapters, we will continue to improve our project: we will add a library and
+ make it configurable.
+*/
+
+/*!
+ \previouspage tutorial-1.html
+ \page tutorial-2.html
+ \nextpage tutorial-3.html
+
+ \title Static Library
+
+ Let's add a static library to our project so we can reuse some code. Analogous to what we did
+ for the application, we put the file into the \c{lib} directory and add it to the
+ \l{Project::references}{references} property in our project. The modified project may look
+ like this:
+
+ \snippet ../tutorial/chapter-2/myproject.qbs 0
+
+ Let's take a look at the the library file now:
+
+ \snippet ../tutorial/chapter-2/lib/lib.qbs 0
+ It contains the \l{StaticLibrary} item which sets the \l{Product::type}{type} of the product
+ to \c "staticlibrary" and sets some defaults like where to install that library.
+ As before, we set the \l{Product::files}{files} property with a header:
+ \snippet ../tutorial/chapter-2/lib/lib.h 0
+ And we set the implementation file of our library:
+ \snippet ../tutorial/chapter-2/lib/lib.c 0
+
+ We will keep our library really simple - it only contains one function, which we will later use
+ in our application. The source file requires a \c "CRUCIAL_DEFINE" to be
+ passed to a preprocessor. That is why we set the \l{cpp::defines}{cpp.defines} property:
+ \snippet ../tutorial/chapter-2/lib/lib.qbs 1
+
+ Note that unlike the \l{CppApplication} item, the \l{StaticLibrary} does not pull in the
+ dependency on the \l{cpp} module automatically - that is why we have to pull it in manually
+ using the \l{Depends} item. Without the \l{cpp} module, \QBS would not know how to turn a
+ \c{.c} file into an object file and the object file into a library. See
+ \l{Rules and Product Types} for details.
+
+ Next, we need to tell \QBS where to look for public headers of our library when building
+ products that depend on it. By default, \QBS knows nothing about the layout of our
+ library, so we tell it to look for headers in the library's source directory using the
+ \l{Export} item:
+
+ \snippet ../tutorial/chapter-2/lib/lib.qbs 2
+ You can export any \l{Module} property within the \l{Export} item - it will be merged in the
+ depending product with other properties. For example, you can export
+ \l{cpp::defines}{cpp.defines} or specific \l{cpp::commonCompilerFlags}{compiler flags} that
+ are required to use this product.
+
+ We depend on the \l{cpp} module twice - once within the \l{StaticLibrary}
+ item and once in the \l{Export} item. This is because by default \QBS does not export anything
+ when depending on this product and the dependencies in this item (as well as
+ properties set in this item) are private to this product while dependencies and properties
+ set in the \l{Export} item are for export only.
+
+ Finally, we have some Apple-specific settings. You can skip this part of the tutorial if you
+ are using some other platform. We depend on the \l{bundle} module and set the
+ \l{bundle::isBundle}{bundle.isBundle} to false:
+ \snippet ../tutorial/chapter-2/lib/lib.qbs 3
+ By default, \QBS builds static and dynamic libraries as
+ \l{https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html}{Frameworks}
+ on macOS. So, to keep things simple, we disable the framework build and build a plain old
+ static library file here.
+*/
+
+/*!
+ \previouspage tutorial-2.html
+ \page tutorial-3.html
+ \nextpage tutorial-4.html
+
+ \title Dynamic Library
+
+ Let's now turn our static library into a dynamic library. It is a bit trickier with dynamic
+ libraries since several things should be tweaked. First, we need to be able to mark methods
+ (or classes) in our library as exported. Second, we need to tell our application where to look
+ for our library at run time using \l{https://en.wikipedia.org/wiki/Rpath}{rpaths}.
+
+ Some compilers, like MSVC, require us to mark which symbols we want to export or import. The
+ \l{https://stackoverflow.com/a/6840659}{canonical} way would be to define some helper macros.
+ Let's start with a header that defines those helper macros:
+ \snippet ../tutorial/chapter-3/lib/lib_global.h 0
+
+ This header defines the \c MYLIB_EXPORT macro that expands either to an "export" or to an
+ "import" directive based on the presence of the \c MYLIB_LIBRARY macro. We can use this macro
+ to mark a function as follows:
+ \snippet ../tutorial/chapter-3/lib/lib.h 0
+
+ The modified library product may look like this:
+
+ \snippet ../tutorial/chapter-3/lib/lib.qbs 0
+
+ The most important change is that we changed the item type from \l{StaticLibrary} to
+ \l{DynamicLibrary}. We also added the \c MYLIB_LIBRARY to the list of
+ \l{cpp::defines}{defines}. We do this only when building the library but we are not exporting
+ it - that way the users of our library will have methods marked for import rather than export.
+
+ Finally, we set \l{cpp::sonamePrefix}{cpp.sonamePrefix} to \c "@rpath". This is required only
+ for Apple platforms, see
+ \l{https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html}{Run-Path Dependent Libraries}
+ for details.
+
+ It is also required to set \l{cpp::rpaths}{cpp.rpaths} in our application file. Since the
+ library is installed to the \c{lib} directory and the application is installed to the \c{bin}
+ directory, we need to tell the loader to look in the \c{lib} directory. The
+ \l{jsextension-fileinfo.html#relativePath}{FileInfo.relativePath} method can help us:
+ \snippet ../tutorial/chapter-3/app/app.qbs 0
+
+ On Linux, this expression would be equivalent to this: \c{cpp.rpaths: ["$ORIGIN/../lib"]}.
+ Don't forget to \c{import qbs.FileInfo} in order to be able to use the
+ \l{jsextension-fileinfo.html} extension.
+
+ To make the example complete, here's how the full \c app/app.qbs file should look like:
+ \snippet ../tutorial/chapter-3/app/app.qbs 1
+*/
+
+/*!
+ \previouspage tutorial-3.html
+ \page tutorial-4.html
+ \nextpage tutorial-5.html
+
+ \title Convenience Items
+
+ As you might have noticed, we are repeating ourselves when setting the same properties in our
+ products - we set \l{Product::version}{version}, \l{Application::install}{install},
+ \l{cpp::rpaths}{cpp.rpaths}, and so on. For a single application and a library, that is not a
+ big deal, but what if we have a dozen libraries? Luckily, this can be achieved using item
+ \l{language-introduction.html#reusing-project-file-code}{inheritance} - we move the common code
+ to the base item and in the real product we will only set what is specific to that product (for
+ example, the list of \l{Product::files}{files}).
+
+ First, we need to tell \QBS where to look for our new base items. This can be achieved using
+ the \l{Project::qbsSearchPaths}{qbsSearchPaths} property. We set this property to \c "qbs" so
+ that \QBS will search our items in the \c{qbs} directory located in the project directory:
+ \snippet ../tutorial/chapter-4/myproject.qbs 0
+
+ \note This directory has a pre-defined structure: base items should be located under the
+ \c{imports} subdirectory. See \l{Custom Modules and Items} for details.
+
+ Let's create a base item for all our applications and move common code there:
+ \snippet ../tutorial/chapter-4/qbs/imports/MyApplication.qbs 0
+
+ As you see, we managed to extract most of the code here, and our application file now only
+ contains what's relevant to it:
+ \snippet ../tutorial/chapter-4/app/app.qbs 0
+
+ Now let's do the same for our library:
+ \snippet ../tutorial/chapter-4/qbs/imports/MyLibrary.qbs 0
+
+ Here, we introduce a helper property, \c libraryMacro, with a default value calculated based
+ on the capitalized product name. Since the name of out library product is \c "mylib", this
+ property will expand to \c "MYLIB_LIBRARY". We can also override the default value
+ for the macro in products that inherit our item like this:
+ \code
+ MyLibrary {
+ libraryMacro: "SOME_OTHER_LIBRARY_MACRO"
+ }
+ \endcode
+
+ Let's take a look at the refactored library file:
+ \snippet ../tutorial/chapter-4/lib/lib.qbs 0
+
+ We managed to extract the reusable parts to common base items leaving the actual products clean
+ and simple.
+
+ Unfortunately, item inheritance comes with a price - when both parent and child items set the
+ same property (\l{cpp::defines}{cpp.defines} in our case), the value in the child item wins.
+ To work around this, the special \l{special-property-values.html#base}{base} value
+ exists - it gives access to the base item's value of the current property and makes it possible
+ to extend its value rather than override it. Here, we concatenate the list of defines from the
+ base item \c{["MYLIB_LIBRARY"]} with a new list, specific to this product (namely,
+ \c{['CRUCIAL_DEFINE']}).
+*/
+
+/*!
+ \previouspage tutorial-4.html
+ \page tutorial-5.html
+ \nextpage tutorial-6.html
+
+ \title Autotest
+
+ Now that we can re-use our base items, let's add a simple autotest. We inherit the new item
+ from the \c MyApplication item and add an \c "autotest" type:
+
+ \snippet ../tutorial/chapter-5/qbs/imports/MyAutoTest.qbs 0
+
+ This additional type is required for the helper \l{AutotestRunner} item. This item runs all
+ products with the \c "autotest" type when it is being built. It also implicitly depends on all
+ such products, so they will be built before running as well.
+
+ Let's add this item to our top-level \l{Project} item:
+
+ \code
+ Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ // ...
+ AutotestRunner {
+ timeout: 60
+ }
+ }
+ \endcode
+ Here we set the \l{AutotestRunner::timeout}{timeout} property to 60 seconds so that our runner
+ kills tests that run way too long.
+
+ Now we need to add our first test. Let's create a new product with the following content:
+
+ \snippet ../tutorial/chapter-5/test/test.qbs 0
+
+ Here we depend on our library (which we are going to test), set the product
+ \l{Product::name}{name}, and specify the source file, which looks like this:
+ \snippet ../tutorial/chapter-5/test/test.c 0
+
+ The test compares the value from the library with the value from the command line.
+
+ Don't forget to add the new test product to the references property in the Project:
+ \snippet ../tutorial/chapter-5/myproject.qbs 0
+
+ Now we can build the autotest product - this will also launch our test:
+ \code
+ $ qbs build -p "autotest-runner"
+ ...
+ running test mytest [autotest-runner]
+ Build done for configuration default.
+ \endcode
+*/
+
+/*!
+ \previouspage tutorial-5.html
+ \page tutorial-6.html
+ \nextpage tutorial-7.html
+
+ \title Project Properties
+
+ It would be nice if our project was configurable. Let's add some properties to our root project
+ file:
+ \snippet ../tutorial/chapter-6/myproject.qbs 0
+
+ Now we can use those properties in our helper items and in products:
+
+ \snippet ../tutorial/chapter-6/qbs/imports/MyApplication.qbs 0
+
+ Here, we access the project file using the special
+ \l{special-property-values.html#project}{project} value. If the nearest project in the project
+ tree does not have the desired property, \QBS looks it up in the parent project, potentially
+ all the way up to the top-level project.
+ We also use the \l{Application::installDebugInformation}{installDebugInformation}
+ property here. By default, it has the same value as \l{Application::install}{install} but we
+ want to make the debug information install optional.
+
+ Let's now disable the tests if \c project.withTests is \c false:
+
+ \snippet ../tutorial/chapter-6/myproject.qbs 1
+
+ Here we use the \l{Properties} item within the \l{SubProject} item. This item allows to
+ override a subproject's properties which can be useful when adding some other \QBS project as a
+ Git submodule. Of course, it is not very useful in our particular case since we only set the
+ \l{Project::condition}{Project.condition} property. We could achieve the same effect by
+ setting the \l{SubProject::condition}{condition} property of the \l{SubProject} item:
+ \code
+ SubProject {
+ filePath: "test/test.qbs"
+ condition: parent.withTests
+ }
+ \endcode
+ Another way would be to disable the test product. That way, an IDE would still show the whole
+ project tree including disabled tests:
+ \code
+ // qbs/imports/MyAutoTest.qbs
+ MyApplication {
+ condition: project.withTests
+ type: base.concat(["autotest"])
+ }
+ \endcode
+
+ Let's finally make our \l{AutotestRunner} configurable too:
+ \snippet ../tutorial/chapter-6/myproject.qbs 2
+
+ There are several ways to override project properties from the command line. First, the special
+ \c project variable can be used to set the properties of the top-level project:
+ \code
+ $ qbs resolve project.withTests:false
+ \endcode
+
+ You can also refer to properties using project's \l{Project::name}{name}:
+
+ \code
+ $ qbs resolve "projects.My Project.withTests:false"
+ \endcode
+
+ This can again be useful for accessing the properties of external sub-projects. Note that since
+ the project name contains spaces, we use quotes here.
+*/
+
+/*!
+ \previouspage tutorial-6.html
+ \page tutorial-7.html
+ \nextpage tutorial-8.html
+
+ \title Buildconfig Module
+
+ In the previous chapter, we added some properties to our main \l{Project} file. While this is a
+ perfect approach for \e public properties of the project, sometimes we want
+ to add some \e private properties for better tuning. Of course, we could put everything
+ in the \l{Project} file, but that would make it very convoluted. Also, accessing the top-level
+ project all the way from products makes things strongly tied.
+
+ You can also use a \l{Module} that \l{Product}{products} may depend on. That way, a
+ \l{Product} only uses properties of the module it depends on without the need to know about
+ the top-level project.
+
+ Let's create a file named \c{mybuildconfig.qbs} and put it into the
+ \c{qbs/modules/mybuildconfig} directory, near the \c{qbs/imports} directory:
+ \code
+ // qbs/modules/mybuildconfig.qbs
+ Module {
+ }
+ \endcode
+
+ So far, this is just an empty \l{Module} so let's add some properties to it:
+ \snippet ../tutorial/chapter-7/qbs/modules/mybuildconfig/mybuildconfig.qbs 0
+
+ We added the \c appInstallDir and \c libInstallDir properties that will allow us to configure
+ the installation location of the our application and library, respectively.
+
+ Now we can use our module in the \c{MyApplication.qbs} item:
+ \snippet ../tutorial/chapter-7/qbs/imports/MyApplication.qbs 0
+
+ We pull in the new module using the \l{Depends} item, similar to how we pulled in
+ the \l{cpp} module dependency earlier. We also set the \l{Application::installDir}{installDir}
+ property to the corresponding module property, namely to \c{mybuildconfig.appInstallDir}.
+
+ \QBS \l{Module}{modules} have the feature to automatically export properties of other modules.
+ Those exported properties are merged in the resulting product. We can use this feature to set
+ the \l{cpp::rpaths}{cpp.rpaths} in our module rather than in products:
+
+ \snippet ../tutorial/chapter-7/qbs/modules/mybuildconfig/mybuildconfig.qbs 1
+
+ Here, we inject the dependency on the \l{cpp} module and calculate the \c{libRPaths} property.
+ This is a relative path from the \c{product.installDir} (which is either \c{"bin"}
+ or \c{"lib"}, depending on product type to \c{libInstallDir}). Finally, we set
+ \l{cpp::rpaths}{cpp.rpaths} to this property. This way, those \c rpaths will be automatically
+ exported to all products that depend on the \c{mybuildconfig} module.
+
+ Now, we can also use our new module in the library item:
+ \snippet ../tutorial/chapter-7/qbs/imports/MyLibrary.qbs 0
+
+ Let's change the library folder name from \c{"lib"} to \c{"lib64"} when building our project:
+ \code
+ $ qbs modules.mybuildconfig.libDirName:lib64
+ ...
+ $ ls default/install-root/usr/local/
+ bin lib64
+ \endcode
+*/
+
+/*!
+ \previouspage tutorial-7.html
+ \page tutorial-8.html
+ \nextpage tutorial-9.html
+
+ \title Configurable Library
+
+ In this chapter, we will make our library more configurable. We will add configuration options
+ to be able to choose between static and dynamic builds.
+
+ We start with some new properties in the \c mybuildconfig module:
+ \code
+ Module {
+ ...
+ property bool staticBuild: false
+ property bool installStaticLib: true
+ ...
+ }
+ \endcode
+
+ We need to do some modifications to export macros in the \c lib/lib_global.h header:
+ \snippet ../tutorial/chapter-8/lib/lib_global.h 0
+
+ Here' we added a new branch when \c MYLIB_STATIC_LIBRARY is defined. In that case, we make
+ the \c MY_LIB_EXPORT macro empty.
+
+ Now, let's modify the \c qbs/imports/MyLibrary.qbs file as follows:
+ \snippet ../tutorial/chapter-8/qbs/imports/MyLibrary.qbs 0
+
+ First thing to notice is that we changed the type of the item from \l{DynamicLibrary} to the
+ \l{Library} item which is a common base item for dynamic and static libraries.
+
+ Second, we change the \l{Product::type}{type} of the product so it depends on the \c staticBuild
+ property:
+ \code
+ type: mybuildconfig.staticBuild ? "staticlibrary" : "dynamiclibrary"
+ \endcode
+
+ Third, we change our \e{export macro} to be different in the static and dynamic builds to
+ correspond with the changes we made to the \c lib_global.h:
+ \code
+ readonly property string _nameUpper : name.replace(" ", "_").toUpperCase()
+ property string libraryMacro: _nameUpper + "_LIBRARY"
+ property string staticLibraryMacro: _nameUpper + "_STATIC_LIBRARY"
+ cpp.defines: mybuildconfig.staticBuild ? [staticLibraryMacro] : [libraryMacro]
+ \endcode
+
+ Note that in a static build we export the \c MYLIB_STATIC_LIBRARY macro so that the dependent
+ products will know whether they link to a static or dynamic library:
+ \code
+ Export {
+ ...
+ cpp.defines: exportingProduct.mybuildconfig.staticBuild
+ ? [exportingProduct.staticLibraryMacro] : []
+ }
+ \endcode
+
+ Now we can build our project in dynamic or static mode:
+ \code
+ $ qbs resolve modules.mybuildconfig.staticBuild:false && qbs build
+ ...
+ $ ls default/install-root/usr/local/lib/
+ libmylib.1.0.0.so
+ libmylib.1.0.so
+ libmylib.1.so
+ libmylib.so
+ $ rm -r default
+ $ qbs resolve modules.mybuildconfig.staticBuild:true && qbs build
+ ...
+ $ $ ls default/install-root/usr/local/lib/
+ libmylib.a
+ \endcode
+*/
+
+/*!
+ \previouspage tutorial-8.html
+ \page tutorial-9.html
+ \nextpage howtos.html
+
+ \title Version Header
+
+ To create new files, such as version headers, during the build, use \l{Rules}. Every command
+ \QBS executes, such as compile or linker commands, is described by a corresponding Rule in a
+ Module or a Product.
+
+ In this section, we will create a simple header with a string constant based on the variable
+ in our project.
+
+ First, we add this variable to our \c mybuildconfig module:
+ \snippet ../tutorial/chapter-9/qbs/modules/mybuildconfig/mybuildconfig.qbs 0
+
+ Next, we create a new file, \c{version.h.in}, located in the \c{version-header} directory. This
+ file contains the template for our header:
+ \snippet ../tutorial/chapter-9/version-header/version.h.in 0
+
+ Now we create a file called \c{version-header.qbs} in the same directory. This file contains
+ a \l{Product} with a \l{Rule} that turns the \c{version.h.in} into a real header.
+ Let's go through the contents of the file:
+ \snippet ../tutorial/chapter-9/version-header/version-header.qbs 0
+
+ First, we import \l{TextFile Service}{TextFile}. We will need this class to read the template
+ and write the resulting header. Second, we declare a new \l{Product} named \c{"version_header"}
+ and with the \c{"hpp"} type. This is the type of the artifact we will create in the Rule.
+ Third, we add the dependency on the \c mybuildconfig module to use the new
+ \c mybuildconfig.productVersion variable.
+
+ We also add a \l{Group} with the template file and assign the \c{version_h_in} tag to the file:
+ \snippet ../tutorial/chapter-9/version-header/version-header.qbs 1
+
+ \QBS Rules work with file tags, instead of working with files directly, which makes
+ it easy to reuse rules. The name of the tag is chosen arbitrarily here. We could use the name
+ of the file as a tag, but to avoid confusion between file name and file tag, we use underscores
+ in the tag instead of dots.
+
+ Now we can create a Rule itself:
+ \snippet ../tutorial/chapter-9/version-header/version-header.qbs 2
+ Here, we specify that our Rule takes files tagged as \c "version_h_in" and produces an
+ \l{Artifact} with the name \c "version.h" and tagged \c "hpp". By default, files are created in
+ the \l{Product::destinationDirectory}{Product.destinationDirectory} folder. We add the \c "hpp"
+ tag for the header as this is the tag the \l{cpp}{cpp module} uses for headers. That way, \QBS
+ can track changes and process our generated file the same way it treats all other
+ headers. Note that earlier we set the product type to \c "hpp" as well. \QBS requires that
+ artifact type should match the product type directly or be accessible via the chain of Rules.
+ Otherwise, the Rule won't be executed. For details, see the \l{Rules and Product Types}
+ section.
+
+ The actual code generation happens in the \l{Rule::prepare}{Rule.prepare} script:
+ \snippet ../tutorial/chapter-9/version-header/version-header.qbs 3
+
+ In this script, we create a \l {JavaScriptCommand} object and set some meta properties, such as
+ the description and highlight. For details about Commands, see
+ \l{Command and JavaScriptCommand}. In the sourceCode variable, we create a JavaScript
+ function that opens the \l{The inputs and outputs Variables}{input file}, reads its content
+ using the \l{TextFile Service}{TextFile} object, replaces the \c "${PRODUCT_VERSION}"
+ placeholder with the actual value in the \c product.mybuildconfig.productVersion variable, and
+ writes the resulting content into the \l{The inputs and outputs Variables}{output file}.
+
+ Finally, we export the \l{Product::buildDirectory}{exportingProduct.buildDirectory} so that
+ products that depend on this product can include our generated header:
+
+ \snippet ../tutorial/chapter-9/version-header/version-header.qbs 4
+
+ The full content of the file should look like this:
+
+ \snippet ../tutorial/chapter-9/version-header/version-header.qbs 5
+
+ Let's now add our Product into the root project so \QBS will be aware of it:
+ \snippet ../tutorial/chapter-9/myproject.qbs 0
+
+ We also need to add the dependency on the \c "version_header" to our application:
+ \snippet ../tutorial/chapter-9/app/app.qbs 0
+
+ Now we can include the header in the \c main.c file and print the contents of the string
+ constant:
+
+ \snippet ../tutorial/chapter-9/app/main.c 0
+
+ Let's try and run our application. You should see something like this:
+ \code
+ $ qbs run -p "My Application"
+ Starting target. Full command line: .../default/install-root/usr/local/bin/myapp
+ Hello, world
+ Hello from library
+ ProductVersion = 1.0.0
+ \endcode
+*/
diff --git a/docker-compose.yml b/docker-compose.yml
index 8d7a78dac..d852cc5e6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -18,141 +18,119 @@ x-default-service: &linux
- SYS_PTRACE
services:
- focal-qt5: &focal-qt5
+ jammy-qt6: &jammy-qt6
<< : *linux
- hostname: focal-qt5
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-5.15.2_1.24.1-0
+ hostname: jammy-qt6
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-qt6-6.5.3_2.1.2-0
build:
- dockerfile: docker/focal/Dockerfile
+ dockerfile: docker/jammy/Dockerfile
context: .
args:
- QT_VERSION: 5.15.2
- QTCREATOR_VERSION: 9.0.2
-
- focal-qt6:
- << : *linux
- hostname: focal-qt6
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-qt6-6.5.0_1.24.1-0
- build:
- dockerfile: docker/focal/Dockerfile
- context: .
- args:
- QT_VERSION: 6.5.0
- QTCREATOR_VERSION: 9.0.2
-
- focal:
- << : *focal-qt5
+ QT_VERSION: 6.5.3
+ QTCREATOR_VERSION: 11.0.3
- focal-qt6-static:
+ jammy-qt5:
<< : *linux
- hostname: focal-qt6-static
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-qt6-static-6.3.1_1.20.1-0
+ hostname: jammy-qt5
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-5.15.2_2.1.2-0
build:
- dockerfile: docker/focal/test-qt6-static.Dockerfile
+ dockerfile: docker/jammy/Dockerfile
context: .
args:
- QT_VERSION: 6.3.1
- QTCREATOR_VERSION: 5.0.3
+ QT_VERSION: 5.15.2
+ QTCREATOR_VERSION: 11.0.3
- focal-android-515:
- << : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-5.15.1-4
- build:
- dockerfile: docker/focal/test-android.Dockerfile
- context: .
- args:
- QT_VERSION: 5.15.1
- ANDROID_NDK_VERSION: 23.0.7599858
+ jammy:
+ << : *jammy-qt6
- focal-android-620:
+ jammy-qt6-static:
<< : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.2.0-4
+ hostname: jammy-qt6-static
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-qt6-static-6.5.0_2.1.2-0
build:
- dockerfile: docker/focal/test-android.Dockerfile
+ dockerfile: docker/jammy/test-qt6-static.Dockerfile
context: .
args:
- QT_VERSION: 6.2.0
- ANDROID_NDK_VERSION: 23.0.7599858
+ QT_VERSION: 6.5.0
+ QTCREATOR_VERSION: 11.0.3
- focal-android-630:
+ jammy-android-515:
<< : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.3.0-0
+ hostname: jammy-android
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-android-5.15.2-0
build:
- dockerfile: docker/focal/test-android.Dockerfile
+ dockerfile: docker/jammy/test-android.Dockerfile
context: .
args:
- QT_VERSION: 6.3.0
+ QT_VERSION: 5.15.2
ANDROID_NDK_VERSION: 23.0.7599858
- focal-android-642:
+ jammy-android-62:
<< : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.4.2-0
+ hostname: jammy-android
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-android-6.2.4-0
build:
- dockerfile: docker/focal/test-android.Dockerfile
+ dockerfile: docker/jammy/test-android.Dockerfile
context: .
args:
- QT_VERSION: 6.4.2
+ QT_VERSION: 6.2.4
ANDROID_NDK_VERSION: 23.0.7599858
- focal-android-650:
+ jammy-android-65:
<< : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.5.0-0
+ hostname: jammy-android
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-android-6.5.0-0
build:
- dockerfile: docker/focal/test-android.Dockerfile
+ dockerfile: docker/jammy/test-android.Dockerfile
context: .
args:
QT_VERSION: 6.5.0
ANDROID_NDK_VERSION: 25.1.8937393
- focal-android-ndk-r19c:
+ jammy-android-ndk-r19c:
<< : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-ndk-r19c-0
+ hostname: jammy-android
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-android-ndk-r19c-0
build:
- dockerfile: docker/focal/test-android-no-qt.Dockerfile
+ dockerfile: docker/jammy/test-android-no-qt.Dockerfile
context: .
args:
ANDROID_NDK_VERSION: 19.2.5345600
- focal-android-ndk-r21e:
+ jammy-android-ndk-r21e:
<< : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-ndk-r21e-0
+ hostname: jammy-android
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-android-ndk-r21e-0
build:
- dockerfile: docker/focal/test-android-no-qt.Dockerfile
+ dockerfile: docker/jammy/test-android-no-qt.Dockerfile
context: .
args:
ANDROID_NDK_VERSION: 21.4.7075529
- focal-android-ndk-r23:
+ jammy-android-ndk-r23:
<< : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-ndk-r23-0
+ hostname: jammy-android
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-android-ndk-r23-0
build:
- dockerfile: docker/focal/test-android-no-qt.Dockerfile
+ dockerfile: docker/jammy/test-android-no-qt.Dockerfile
context: .
args:
ANDROID_NDK_VERSION: 23.0.7599858
- focal-baremetal:
+ jammy-baremetal:
<< : *linux
- hostname: focal-baremetal
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-baremetal-4
+ hostname: jammy-baremetal
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-baremetal-0
build:
- dockerfile: docker/focal/test-baremetal.Dockerfile
+ dockerfile: docker/jammy/test-baremetal.Dockerfile
context: .
- focal-qt4:
+ jammy-qt4:
<< : *linux
- hostname: focal-qt4
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-qt4-1
+ hostname: jammy-qt4
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:jammy-qt4-0
build:
- dockerfile: docker/focal/test-qt4.Dockerfile
+ dockerfile: docker/jammy/test-qt4.Dockerfile
context: .
leap:
@@ -167,12 +145,12 @@ services:
QTCREATOR_VERSION: 5.0.3
windows:
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:windowsservercore-6.2.4_1.24.0-0
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:windowsservercore-6.6.0_1.24.0-0
build:
dockerfile: docker/windowsservercore/Dockerfile
context: .
args:
- QT_VERSION: 6.2.4
+ QT_VERSION: 6.6.0
QTCREATOR_VERSION: 9.0.1
working_dir: 'C:/qbs'
environment:
diff --git a/docker/focal/entrypoint.sh b/docker/entrypoint.sh
index 40bc5acb9..40bc5acb9 100755
--- a/docker/focal/entrypoint.sh
+++ b/docker/entrypoint.sh
diff --git a/docker/focal/Dockerfile b/docker/jammy/Dockerfile
index 917d4d0a7..cdda42202 100644
--- a/docker/focal/Dockerfile
+++ b/docker/jammy/Dockerfile
@@ -1,7 +1,7 @@
#
# Install Qt and Qbs for Linux
#
-FROM ubuntu:focal
+FROM ubuntu:jammy
LABEL Description="Ubuntu development environment for Qbs with Qt and various dependencies for testing Qbs modules and functionality"
ARG QT_VERSION
ARG QTCREATOR_VERSION
@@ -29,10 +29,10 @@ RUN apt-get update -qq && \
usermod -a -G sudo ${USER_NAME} && \
echo "%devel ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers
-COPY docker/focal/entrypoint.sh /sbin/entrypoint.sh
+COPY docker/entrypoint.sh /sbin/entrypoint.sh
ENTRYPOINT ["/sbin/entrypoint.sh"]
-# Qbs build dependencies
+# # Qbs build dependencies
RUN apt-get update -qq && \
DEBIAN_FRONTEND="noninteractive" apt-get install -qq -y --no-install-recommends \
bison \
@@ -40,8 +40,8 @@ RUN apt-get update -qq && \
ca-certificates \
capnproto \
ccache \
- clang-12 \
- clang-tidy-12 \
+ clang-15 \
+ clang-tidy-15 \
cmake \
curl \
flex \
@@ -72,9 +72,9 @@ RUN apt-get update -qq && \
subversion \
unzip \
zip && \
- update-alternatives --install /usr/bin/clang clang /usr/bin/clang-12 100 && \
- update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-12 100 && \
- update-alternatives --install /usr/bin/clang-check clang-check /usr/bin/clang-check-12 100 && \
+ update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 100 && \
+ update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 100 && \
+ update-alternatives --install /usr/bin/clang-check clang-check /usr/bin/clang-check-15 100 && \
update-alternatives --install /usr/bin/python python /usr/bin/python3 100 && \
pip install beautifulsoup4 lxml protobuf==3.19.1 pyyaml
@@ -85,8 +85,7 @@ ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
-ENV LLVM_INSTALL_DIR=/usr/lib/llvm-12
-
+ENV LLVM_INSTALL_DIR=/usr/lib/llvm-15
#
# Install Qt and Qbs for Linux from qt.io
diff --git a/docker/focal/test-android-no-qt.Dockerfile b/docker/jammy/test-android-no-qt.Dockerfile
index e1622043a..4e6dadf4f 100644
--- a/docker/focal/test-android-no-qt.Dockerfile
+++ b/docker/jammy/test-android-no-qt.Dockerfile
@@ -1,7 +1,7 @@
#
# Android SDK/NDK for testing Qbs
#
-FROM ubuntu:focal
+FROM ubuntu:jammy
LABEL Description="Ubuntu test environment for Qbs for Android"
# Allow colored output on command line.
@@ -27,7 +27,7 @@ RUN apt-get update -qq && \
usermod -a -G sudo ${USER_NAME} && \
echo "%devel ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers
-COPY docker/focal/entrypoint.sh /sbin/entrypoint.sh
+COPY docker/entrypoint.sh /sbin/entrypoint.sh
ENTRYPOINT ["/sbin/entrypoint.sh"]
# Qbs build dependencies
diff --git a/docker/focal/test-android.Dockerfile b/docker/jammy/test-android.Dockerfile
index 5f479e4fc..b17cee65b 100644
--- a/docker/focal/test-android.Dockerfile
+++ b/docker/jammy/test-android.Dockerfile
@@ -1,7 +1,7 @@
#
# Android SDK/NDK + Qt for Android for testing Qbs
#
-FROM ubuntu:focal
+FROM ubuntu:jammy
LABEL Description="Ubuntu test environment for Qbs and Qt for Android"
# Allow colored output on command line.
@@ -27,7 +27,7 @@ RUN apt-get update -qq && \
usermod -a -G sudo ${USER_NAME} && \
echo "%devel ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers
-COPY docker/focal/entrypoint.sh /sbin/entrypoint.sh
+COPY docker/entrypoint.sh /sbin/entrypoint.sh
ENTRYPOINT ["/sbin/entrypoint.sh"]
# Qbs build dependencies
diff --git a/docker/focal/test-baremetal.Dockerfile b/docker/jammy/test-baremetal.Dockerfile
index 5bef208ca..774885797 100644
--- a/docker/focal/test-baremetal.Dockerfile
+++ b/docker/jammy/test-baremetal.Dockerfile
@@ -1,7 +1,7 @@
#
# Baremetal toolchains for testing Qbs
#
-FROM ubuntu:focal
+FROM ubuntu:jammy
LABEL Description="Ubuntu baremetal test environment for Qbs"
# Allow colored output on command line.
@@ -27,7 +27,7 @@ RUN apt-get update -qq && \
usermod -a -G sudo ${USER_NAME} && \
echo "%devel ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers
-COPY docker/focal/entrypoint.sh /sbin/entrypoint.sh
+COPY docker/entrypoint.sh /sbin/entrypoint.sh
ENTRYPOINT ["/sbin/entrypoint.sh"]
# Install baremetal toolchains and Qbs runtime dependencies.
diff --git a/docker/focal/test-qt4.Dockerfile b/docker/jammy/test-qt4.Dockerfile
index e49e255f9..9fa8f5d6b 100644
--- a/docker/focal/test-qt4.Dockerfile
+++ b/docker/jammy/test-qt4.Dockerfile
@@ -1,7 +1,7 @@
#
# Testing Qbs with qt4
#
-FROM ubuntu:focal
+FROM ubuntu:jammy
LABEL Description="Ubuntu qt4 test environment for Qbs"
# Allow colored output on command line.
@@ -28,11 +28,11 @@ RUN apt-get update -qq && \
usermod -a -G sudo ${USER_NAME} && \
echo "%devel ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers
-COPY docker/focal/entrypoint.sh /sbin/entrypoint.sh
+COPY docker/entrypoint.sh /sbin/entrypoint.sh
ENTRYPOINT ["/sbin/entrypoint.sh"]
# Install baremetal toolchains and Qbs runtime dependencies.
-RUN sudo add-apt-repository ppa:gezakovacs/ppa -y && \
+RUN sudo add-apt-repository ppa:ubuntuhandbook1/ppa -y && \
apt-get update -qq && \
apt-get install -qq -y \
build-essential \
diff --git a/docker/focal/test-qt6-static.Dockerfile b/docker/jammy/test-qt6-static.Dockerfile
index 61316e372..37239fd9a 100644
--- a/docker/focal/test-qt6-static.Dockerfile
+++ b/docker/jammy/test-qt6-static.Dockerfile
@@ -1,7 +1,7 @@
#
# Testing Qbs with static qt6
#
-FROM ubuntu:focal
+FROM ubuntu:jammy
LABEL Description="Ubuntu static qt6 test environment for Qbs"
ARG QT_VERSION
ARG QTCREATOR_VERSION
@@ -31,11 +31,11 @@ RUN apt-get update -qq && \
usermod -a -G sudo ${USER_NAME} && \
echo "%devel ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers
-COPY docker/focal/entrypoint.sh /sbin/entrypoint.sh
+COPY docker/entrypoint.sh /sbin/entrypoint.sh
ENTRYPOINT ["/sbin/entrypoint.sh"]
RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
-RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
+RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
RUN cat /etc/apt/sources.list.d/kitware.list
RUN apt-get update -qq && \
@@ -43,8 +43,9 @@ RUN apt-get update -qq && \
build-essential \
git \
perl \
+ clang-15 \
cmake \
- python \
+ python3 \
zlib1g-dev \
libzstd-dev \
libdbus-1-dev \
@@ -56,6 +57,7 @@ RUN apt-get update -qq && \
libvulkan-dev \
libicu-dev \
libb2-dev \
+ libclang-15-dev \
libsystemd-dev \
libfontconfig1-dev \
libfreetype6-dev \
@@ -101,6 +103,7 @@ RUN apt-get update -qq && \
libxcb-damage0-dev \
libxcb-dpms0-dev \
libgstreamer1.0-dev \
+ llvm-15-dev \
apt-transport-https
ENV QT_HOME="/home/${USER_NAME}/qt"
@@ -119,7 +122,7 @@ USER root
RUN cd ${QT_HOME}/static-build && cmake --install .
-FROM ubuntu:focal
+FROM ubuntu:jammy
LABEL Description="Ubuntu static qt6 test environment for Qbs"
ARG QT_VERSION
ARG QTCREATOR_VERSION
@@ -153,8 +156,8 @@ RUN apt-get update -qq && \
ca-certificates \
capnproto \
ccache \
- clang-8 \
- clang-tidy-8 \
+ clang-15 \
+ clang-tidy-15 \
cmake \
curl \
flex \
@@ -186,13 +189,13 @@ RUN apt-get update -qq && \
zip \
libb2-1 \
libpcre++ && \
- update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 100 && \
- update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 100 && \
- update-alternatives --install /usr/bin/clang-check clang-check /usr/bin/clang-check-8 100 && \
+ update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 100 && \
+ update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 100 && \
+ update-alternatives --install /usr/bin/clang-check clang-check /usr/bin/clang-check-15 100 && \
update-alternatives --install /usr/bin/python python /usr/bin/python3 100 && \
pip install beautifulsoup4 lxml protobuf pyyaml
-ENV LLVM_INSTALL_DIR=/usr/lib/llvm-8
+ENV LLVM_INSTALL_DIR=/usr/lib/llvm-15
#
# Install Qbs for Linux from qt.io
diff --git a/examples/app-and-lib/app/app.qbs b/examples/app-and-lib/app/app.qbs
index dcb9e7af2..1cdb95c39 100644
--- a/examples/app-and-lib/app/app.qbs
+++ b/examples/app-and-lib/app/app.qbs
@@ -53,5 +53,6 @@ Application {
files : [ "main.cpp" ]
Depends { name: "cpp" }
Depends { name: "mylib" }
+ install: true
}
diff --git a/examples/cocoa-touch-application/cocoa-touch-application.qbs b/examples/cocoa-touch-application/cocoa-touch-application.qbs
index ec199d4a5..c837cacf1 100644
--- a/examples/cocoa-touch-application/cocoa-touch-application.qbs
+++ b/examples/cocoa-touch-application/cocoa-touch-application.qbs
@@ -64,6 +64,11 @@ CppApplication {
cpp.automaticReferenceCounting: true
cpp.frameworks: [ "UIKit", "Foundation", "CoreGraphics" ]
+ // sample code signing settings:
+ // codesign.enableCodeSigning: true
+ // codesign.provisioningProfile: "my provisioning profile name"
+ // codesign.signingIdentity: "Apple Development: My Team Name"
+
Group {
prefix: "CocoaTouchApplication/"
files: [
diff --git a/examples/code-generator/code-generator.qbs b/examples/code-generator/code-generator.qbs
index 4d664c0ec..fdb0e1aac 100644
--- a/examples/code-generator/code-generator.qbs
+++ b/examples/code-generator/code-generator.qbs
@@ -62,7 +62,7 @@ Project {
// Generate and build a hello-world application.
CppApplication {
- condition: qbs.targetOS === qbs.hostOS
+ condition: qbs.targetPlatform === qbs.hostPlatform
name: "hello-world"
Depends { name: "hwgen" }
Rule {
diff --git a/examples/examples.qbs b/examples/examples.qbs
index d457b8a88..fa8ed0f5c 100644
--- a/examples/examples.qbs
+++ b/examples/examples.qbs
@@ -48,24 +48,6 @@
**
****************************************************************************/
-Project {
- references: [
- "app-and-lib/app-and-lib.qbs",
- "cocoa-application/cocoa-application.qbs",
- "cocoa-touch-application/cocoa-touch-application.qbs",
- "code-generator/code-generator.qbs",
- "collidingmice/collidingmice.qbs",
- "compiled-qml/compiled-qml.qbs",
- "grpc/grpc.qbs",
- "helloworld-complex/helloworld-complex.qbs",
- "helloworld-minimal/helloworld-minimal.qbs",
- "helloworld-qt/helloworld-qt.qbs",
- "install-bundle/install-bundle.qbs",
- // these examples are broken because of the bug in the protobuf modules
- // "protobuf/addressbook_cpp/addressbook_cpp.qbs",
- // "protobuf/addressbook_objc/addressbook_objc.qbs",
- "baremetal/baremetal.qbs",
- "rule/rule.qbs",
- "rpaths/rpaths.qbs",
- ]
+Product {
+ files: "**/*"
}
diff --git a/examples/exporters/exporters.qbs b/examples/exporters/exporters.qbs
new file mode 100644
index 000000000..bae62e17c
--- /dev/null
+++ b/examples/exporters/exporters.qbs
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+Project {
+ references: [
+ "lib_a/lib_a.qbs",
+ "lib_b/lib_b.qbs"
+ ]
+ qbsSearchPaths: "qbs"
+}
+
diff --git a/examples/exporters/lib_a/lib_a.cpp b/examples/exporters/lib_a/lib_a.cpp
new file mode 100644
index 000000000..e3e7f17d9
--- /dev/null
+++ b/examples/exporters/lib_a/lib_a.cpp
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "lib_a.h"
+
+const char *bla()
+{
+ return "Hello World!";
+}
diff --git a/examples/exporters/lib_a/lib_a.h b/examples/exporters/lib_a/lib_a.h
new file mode 100644
index 000000000..f583421b9
--- /dev/null
+++ b/examples/exporters/lib_a/lib_a.h
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef LIB_A_H
+#define LIB_A_H
+
+const char *bla();
+
+#endif // LIB_A_H
diff --git a/examples/exporters/lib_a/lib_a.qbs b/examples/exporters/lib_a/lib_a.qbs
new file mode 100644
index 000000000..fedbb7074
--- /dev/null
+++ b/examples/exporters/lib_a/lib_a.qbs
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+MyLibrary {
+ name: "lib_a"
+ files: ["lib_a.cpp"]
+
+ Group {
+ name: "API headers"
+ files: ["lib_a.h"]
+ qbs.install: true
+ qbs.installDir: headersInstallDir
+ }
+ Export {
+ Depends { name: "cpp" }
+ cpp.dynamicLibraries: "z"
+ cpp.libraryPaths: "/opt/local/lib"
+ }
+}
+
diff --git a/examples/exporters/lib_b/lib_b.cpp b/examples/exporters/lib_b/lib_b.cpp
new file mode 100644
index 000000000..b29dc57b6
--- /dev/null
+++ b/examples/exporters/lib_b/lib_b.cpp
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "lib_b.h"
+
+#include <lib_a.h>
+
+#include <cstdio>
+
+int foo()
+{
+ std::puts(bla());
+ return 2;
+}
diff --git a/examples/exporters/lib_b/lib_b.h b/examples/exporters/lib_b/lib_b.h
new file mode 100644
index 000000000..a3d3e3aee
--- /dev/null
+++ b/examples/exporters/lib_b/lib_b.h
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef LIB_B_H
+#define LIB_B_H
+
+int foo();
+
+#endif // LIB_B_H
diff --git a/examples/exporters/lib_b/lib_b.qbs b/examples/exporters/lib_b/lib_b.qbs
new file mode 100644
index 000000000..0c680f3e0
--- /dev/null
+++ b/examples/exporters/lib_b/lib_b.qbs
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+MyLibrary {
+ Depends { name: "lib_a" }
+ name: "lib_b"
+ files: ["lib_b.cpp"]
+ Group {
+ name: "API headers"
+ files: ["lib_b.h"]
+ qbs.install: true
+ qbs.installDir: headersInstallDir
+ }
+ Export {
+ Depends { name: 'lib_a' }
+ }
+}
diff --git a/examples/exporters/qbs/imports/MyLibrary.qbs b/examples/exporters/qbs/imports/MyLibrary.qbs
new file mode 100644
index 000000000..4607776b3
--- /dev/null
+++ b/examples/exporters/qbs/imports/MyLibrary.qbs
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com).
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import qbs.FileInfo
+
+StaticLibrary {
+ Depends { name: 'cpp' }
+
+ property string headersInstallDir: "include"
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ prefixMapping: [{
+ prefix: exportingProduct.sourceDirectory,
+ replacement: FileInfo.joinPaths(exportingProduct.qbs.installPrefix,
+ exportingProduct.headersInstallDir)
+ }]
+ }
+ install: true
+
+ Depends { name: "Exporter.qbs" }
+
+ Group {
+ fileTagsFilter: "Exporter.qbs.module"
+ qbs.install: install
+ qbs.installDir: FileInfo.joinPaths(installDir, "qbs", "modules", product.name)
+ }
+
+ Depends { name: "Exporter.pkgconfig" }
+ Exporter.pkgconfig.versionEntry: "1.0"
+
+ Group {
+ fileTagsFilter: ["Exporter.pkgconfig.pc"]
+ qbs.install: install
+ qbs.installDir: FileInfo.joinPaths(installDir, "pkgconfig")
+ }
+
+ Depends { name: "Exporter.cmake" }
+ Group {
+ fileTagsFilter: ["Exporter.cmake.package"]
+ qbs.install: install
+ qbs.installDir: FileInfo.joinPaths(installDir, "cmake", product.name)
+ }
+
+ Depends { name: 'bundle' }
+ bundle.isBundle: false
+}
+
diff --git a/examples/grpc/grpc.qbs b/examples/grpc/grpc.qbs
index c4f146ec0..b6d169508 100644
--- a/examples/grpc/grpc.qbs
+++ b/examples/grpc/grpc.qbs
@@ -32,15 +32,16 @@ import qbs.Utilities
Project {
condition: Utilities.versionCompare(qbs.version, "1.14") >= 0
+ qbsModuleProviders: "qbspkgconfig"
Application {
Depends { name: "cpp" }
Depends { name: "protobuf.cpp"; required: false }
- condition: protobuf.cpp.present && qbs.targetOS === qbs.hostOS
+ condition: protobuf.cpp.present && qbs.targetPlatform === qbs.hostPlatform
protobuf.cpp.useGrpc: true
consoleApplication: true
cpp.cxxLanguageVersion: "c++17"
- cpp.minimumMacosVersion: "10.8"
+ cpp.minimumMacosVersion: "10.15"
name: "client"
files: "client.cpp"
Properties {
@@ -56,11 +57,11 @@ Project {
Application {
Depends { name: "cpp" }
Depends { name: "protobuf.cpp"; required: false }
- condition: protobuf.cpp.present && qbs.targetOS === qbs.hostOS
+ condition: protobuf.cpp.present && qbs.targetPlatform === qbs.hostPlatform
protobuf.cpp.useGrpc: true
consoleApplication: true
cpp.cxxLanguageVersion: "c++17"
- cpp.minimumMacosVersion: "10.8"
+ cpp.minimumMacosVersion: "10.15"
name: "server"
files: "server.cpp"
Properties {
diff --git a/examples/protobuf/addressbook_cpp/addressbook_cpp.qbs b/examples/protobuf/addressbook_cpp/addressbook_cpp.qbs
index 667b30223..5a3b27652 100644
--- a/examples/protobuf/addressbook_cpp/addressbook_cpp.qbs
+++ b/examples/protobuf/addressbook_cpp/addressbook_cpp.qbs
@@ -5,8 +5,7 @@ CppApplication {
condition: protobuf.cpp.present && qbs.targetPlatform === Host.platform()
Depends { name: "cpp" }
- cpp.cxxLanguageVersion: "c++11"
- cpp.minimumMacosVersion: "10.8"
+ cpp.minimumMacosVersion: "10.15"
Depends { name: "protobuf.cpp"; required: false }
@@ -15,4 +14,5 @@ CppApplication {
"main.cpp",
"README.md",
]
+ qbsModuleProviders: "qbspkgconfig"
}
diff --git a/qbs-resources/imports/QbsProduct.qbs b/qbs-resources/imports/QbsProduct.qbs
index 5df694dd8..ea8516736 100644
--- a/qbs-resources/imports/QbsProduct.qbs
+++ b/qbs-resources/imports/QbsProduct.qbs
@@ -2,7 +2,7 @@ Product {
Depends { name: "qbsbuildconfig" }
Depends { name: "qbsversion" }
Depends { name: "Qt.core"; versionAtLeast: minimumQtVersion }
- property string minimumQtVersion: "5.14.0"
+ property string minimumQtVersion: "5.15.2"
property bool install: true
property string targetInstallDir
cpp.defines: {
diff --git a/qbs.qbs b/qbs.qbs
index 69da6af03..4b2a184db 100644
--- a/qbs.qbs
+++ b/qbs.qbs
@@ -3,15 +3,16 @@ Project {
qbsSearchPaths: ["qbs-resources"]
property bool withCode: true
property bool withDocumentation: true
- property bool withExamples: false
property bool withTests: withCode
property stringList autotestArguments: []
property stringList autotestWrapper: []
references: [
"docker/docker.qbs",
+ "examples/examples.qbs",
"share/share.qbs",
"scripts/scripts.qbs",
+ "tutorial/tutorial.qbs",
]
SubProject {
@@ -22,13 +23,6 @@ Project {
}
SubProject {
- filePath: "examples/examples.qbs"
- Properties {
- condition: parent.withExamples
- }
- }
-
- SubProject {
filePath: "src/src.qbs"
Properties {
condition: parent.withCode
diff --git a/scripts/build-qbs-with-cmake.sh b/scripts/build-qbs-with-cmake.sh
index 820f30303..7f3fe3f5e 100755
--- a/scripts/build-qbs-with-cmake.sh
+++ b/scripts/build-qbs-with-cmake.sh
@@ -100,6 +100,6 @@ fi
#
(while true; do echo "" && sleep 590; done) &
trap "kill $!; wait $! 2>/dev/null || true; killall sleep || true" EXIT
-ctest -j $(nproc --all)
+ctest -j $(nproc --all) --output-on-failure
popd
diff --git a/scripts/git-clang-format b/scripts/git-clang-format
new file mode 100755
index 000000000..d33fd478d
--- /dev/null
+++ b/scripts/git-clang-format
@@ -0,0 +1,683 @@
+#!/usr/bin/env python3
+#
+#===- git-clang-format - ClangFormat Git Integration ---------*- python -*--===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+#===------------------------------------------------------------------------===#
+
+r"""
+clang-format git integration
+============================
+
+This file provides a clang-format integration for git. Put it somewhere in your
+path and ensure that it is executable. Then, "git clang-format" will invoke
+clang-format on the changes in current files or a specific commit.
+
+For further details, run:
+git clang-format -h
+
+Requires Python 2.7 or Python 3
+"""
+
+from __future__ import absolute_import, division, print_function
+import argparse
+import collections
+import contextlib
+import errno
+import os
+import re
+import subprocess
+import sys
+
+usage = ('git clang-format [OPTIONS] [<commit>] [<commit>|--staged] '
+ '[--] [<file>...]')
+
+desc = '''
+If zero or one commits are given, run clang-format on all lines that differ
+between the working directory and <commit>, which defaults to HEAD. Changes are
+only applied to the working directory, or in the stage/index.
+
+Examples:
+ To format staged changes, i.e everything that's been `git add`ed:
+ git clang-format
+
+ To also format everything touched in the most recent commit:
+ git clang-format HEAD~1
+
+ If you're on a branch off main, to format everything touched on your branch:
+ git clang-format main
+
+If two commits are given (requires --diff), run clang-format on all lines in the
+second <commit> that differ from the first <commit>.
+
+The following git-config settings set the default of the corresponding option:
+ clangFormat.binary
+ clangFormat.commit
+ clangFormat.extensions
+ clangFormat.style
+'''
+
+# Name of the temporary index file in which save the output of clang-format.
+# This file is created within the .git directory.
+temp_index_basename = 'clang-format-index'
+
+
+Range = collections.namedtuple('Range', 'start, count')
+
+
+def main():
+ config = load_git_config()
+
+ # In order to keep '--' yet allow options after positionals, we need to
+ # check for '--' ourselves. (Setting nargs='*' throws away the '--', while
+ # nargs=argparse.REMAINDER disallows options after positionals.)
+ argv = sys.argv[1:]
+ try:
+ idx = argv.index('--')
+ except ValueError:
+ dash_dash = []
+ else:
+ dash_dash = argv[idx:]
+ argv = argv[:idx]
+
+ default_extensions = ','.join([
+ # From clang/lib/Frontend/FrontendOptions.cpp, all lower case
+ 'c', 'h', # C
+ 'm', # ObjC
+ 'mm', # ObjC++
+ 'cc', 'cp', 'cpp', 'c++', 'cxx', 'hh', 'hpp', 'hxx', 'inc', # C++
+ 'ccm', 'cppm', 'cxxm', 'c++m', # C++ Modules
+ 'cu', 'cuh', # CUDA
+ # Other languages that clang-format supports
+ 'proto', 'protodevel', # Protocol Buffers
+ 'java', # Java
+ 'js', # JavaScript
+ 'ts', # TypeScript
+ 'cs', # C Sharp
+ 'json', # Json
+ 'sv', 'svh', 'v', 'vh', # Verilog
+ ])
+
+ p = argparse.ArgumentParser(
+ usage=usage, formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=desc)
+ p.add_argument('--binary',
+ default=config.get('clangformat.binary', 'clang-format'),
+ help='path to clang-format'),
+ p.add_argument('--commit',
+ default=config.get('clangformat.commit', 'HEAD'),
+ help='default commit to use if none is specified'),
+ p.add_argument('--diff', action='store_true',
+ help='print a diff instead of applying the changes')
+ p.add_argument('--diffstat', action='store_true',
+ help='print a diffstat instead of applying the changes')
+ p.add_argument('--extensions',
+ default=config.get('clangformat.extensions',
+ default_extensions),
+ help=('comma-separated list of file extensions to format, '
+ 'excluding the period and case-insensitive')),
+ p.add_argument('-f', '--force', action='store_true',
+ help='allow changes to unstaged files')
+ p.add_argument('-p', '--patch', action='store_true',
+ help='select hunks interactively')
+ p.add_argument('-q', '--quiet', action='count', default=0,
+ help='print less information')
+ p.add_argument('--staged', '--cached', action='store_true',
+ help='format lines in the stage instead of the working dir')
+ p.add_argument('--style',
+ default=config.get('clangformat.style', None),
+ help='passed to clang-format'),
+ p.add_argument('-v', '--verbose', action='count', default=0,
+ help='print extra information')
+ p.add_argument('--diff_from_common_commit', action='store_true',
+ help=('diff from the last common commit for commits in '
+ 'separate branches rather than the exact point of the '
+ 'commits'))
+ # We gather all the remaining positional arguments into 'args' since we need
+ # to use some heuristics to determine whether or not <commit> was present.
+ # However, to print pretty messages, we make use of metavar and help.
+ p.add_argument('args', nargs='*', metavar='<commit>',
+ help='revision from which to compute the diff')
+ p.add_argument('ignored', nargs='*', metavar='<file>...',
+ help='if specified, only consider differences in these files')
+ opts = p.parse_args(argv)
+
+ opts.verbose -= opts.quiet
+ del opts.quiet
+
+ commits, files = interpret_args(opts.args, dash_dash, opts.commit)
+ if len(commits) > 2:
+ die('at most two commits allowed; %d given' % len(commits))
+ if len(commits) == 2:
+ if opts.staged:
+ die('--staged is not allowed when two commits are given')
+ if not opts.diff:
+ die('--diff is required when two commits are given')
+ elif opts.diff_from_common_commit:
+ die('--diff_from_common_commit is only allowed when two commits are given')
+
+ if os.path.dirname(opts.binary):
+ opts.binary = os.path.abspath(opts.binary)
+
+ changed_lines = compute_diff_and_extract_lines(commits,
+ files,
+ opts.staged,
+ opts.diff_from_common_commit)
+ if opts.verbose >= 1:
+ ignored_files = set(changed_lines)
+ filter_by_extension(changed_lines, opts.extensions.lower().split(','))
+ # The computed diff outputs absolute paths, so we must cd before accessing
+ # those files.
+ cd_to_toplevel()
+ filter_symlinks(changed_lines)
+ if opts.verbose >= 1:
+ ignored_files.difference_update(changed_lines)
+ if ignored_files:
+ print(
+ 'Ignoring changes in the following files (wrong extension or symlink):')
+ for filename in ignored_files:
+ print(' %s' % filename)
+ if changed_lines:
+ print('Running clang-format on the following files:')
+ for filename in changed_lines:
+ print(' %s' % filename)
+
+ if not changed_lines:
+ if opts.verbose >= 0:
+ print('no modified files to format')
+ return 0
+
+ if len(commits) > 1:
+ old_tree = commits[1]
+ revision = old_tree
+ elif opts.staged:
+ old_tree = create_tree_from_index(changed_lines)
+ revision = ''
+ else:
+ old_tree = create_tree_from_workdir(changed_lines)
+ revision = None
+ new_tree = run_clang_format_and_save_to_tree(changed_lines,
+ revision,
+ binary=opts.binary,
+ style=opts.style)
+ if opts.verbose >= 1:
+ print('old tree: %s' % old_tree)
+ print('new tree: %s' % new_tree)
+
+ if old_tree == new_tree:
+ if opts.verbose >= 0:
+ print('clang-format did not modify any files')
+ return 0
+
+ if opts.diff:
+ return print_diff(old_tree, new_tree)
+ if opts.diffstat:
+ return print_diffstat(old_tree, new_tree)
+
+ changed_files = apply_changes(old_tree, new_tree, force=opts.force,
+ patch_mode=opts.patch)
+ if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1:
+ print('changed files:')
+ for filename in changed_files:
+ print(' %s' % filename)
+
+ return 1
+
+
+def load_git_config(non_string_options=None):
+ """Return the git configuration as a dictionary.
+
+ All options are assumed to be strings unless in `non_string_options`, in which
+ is a dictionary mapping option name (in lower case) to either "--bool" or
+ "--int"."""
+ if non_string_options is None:
+ non_string_options = {}
+ out = {}
+ for entry in run('git', 'config', '--list', '--null').split('\0'):
+ if entry:
+ if '\n' in entry:
+ name, value = entry.split('\n', 1)
+ else:
+ # A setting with no '=' ('\n' with --null) is implicitly 'true'
+ name = entry
+ value = 'true'
+ if name in non_string_options:
+ value = run('git', 'config', non_string_options[name], name)
+ out[name] = value
+ return out
+
+
+def interpret_args(args, dash_dash, default_commit):
+ """Interpret `args` as "[commits] [--] [files]" and return (commits, files).
+
+ It is assumed that "--" and everything that follows has been removed from
+ args and placed in `dash_dash`.
+
+ If "--" is present (i.e., `dash_dash` is non-empty), the arguments to its
+ left (if present) are taken as commits. Otherwise, the arguments are checked
+ from left to right if they are commits or files. If commits are not given,
+ a list with `default_commit` is used."""
+ if dash_dash:
+ if len(args) == 0:
+ commits = [default_commit]
+ else:
+ commits = args
+ for commit in commits:
+ object_type = get_object_type(commit)
+ if object_type not in ('commit', 'tag'):
+ if object_type is None:
+ die("'%s' is not a commit" % commit)
+ else:
+ die("'%s' is a %s, but a commit was expected" % (commit, object_type))
+ files = dash_dash[1:]
+ elif args:
+ commits = []
+ while args:
+ if not disambiguate_revision(args[0]):
+ break
+ commits.append(args.pop(0))
+ if not commits:
+ commits = [default_commit]
+ files = args
+ else:
+ commits = [default_commit]
+ files = []
+ return commits, files
+
+
+def disambiguate_revision(value):
+ """Returns True if `value` is a revision, False if it is a file, or dies."""
+ # If `value` is ambiguous (neither a commit nor a file), the following
+ # command will die with an appropriate error message.
+ run('git', 'rev-parse', value, verbose=False)
+ object_type = get_object_type(value)
+ if object_type is None:
+ return False
+ if object_type in ('commit', 'tag'):
+ return True
+ die('`%s` is a %s, but a commit or filename was expected' %
+ (value, object_type))
+
+
+def get_object_type(value):
+ """Returns a string description of an object's type, or None if it is not
+ a valid git object."""
+ cmd = ['git', 'cat-file', '-t', value]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ if p.returncode != 0:
+ return None
+ return convert_string(stdout.strip())
+
+
+def compute_diff_and_extract_lines(commits, files, staged, diff_common_commit):
+ """Calls compute_diff() followed by extract_lines()."""
+ diff_process = compute_diff(commits, files, staged, diff_common_commit)
+ changed_lines = extract_lines(diff_process.stdout)
+ diff_process.stdout.close()
+ diff_process.wait()
+ if diff_process.returncode != 0:
+ # Assume error was already printed to stderr.
+ sys.exit(2)
+ return changed_lines
+
+
+def compute_diff(commits, files, staged, diff_common_commit):
+ """Return a subprocess object producing the diff from `commits`.
+
+ The return value's `stdin` file object will produce a patch with the
+ differences between the working directory (or stage if --staged is used) and
+ the first commit if a single one was specified, or the difference between
+ both specified commits, filtered on `files` (if non-empty).
+ Zero context lines are used in the patch."""
+ git_tool = 'diff-index'
+ extra_args = []
+ if len(commits) == 2:
+ git_tool = 'diff-tree'
+ if diff_common_commit:
+ commits = [f'{commits[0]}...{commits[1]}']
+ elif staged:
+ extra_args += ['--cached']
+
+ cmd = ['git', git_tool, '-p', '-U0'] + extra_args + commits + ['--']
+ cmd.extend(files)
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ p.stdin.close()
+ return p
+
+
+def extract_lines(patch_file):
+ """Extract the changed lines in `patch_file`.
+
+ The return value is a dictionary mapping filename to a list of (start_line,
+ line_count) pairs.
+
+ The input must have been produced with ``-U0``, meaning unidiff format with
+ zero lines of context. The return value is a dict mapping filename to a
+ list of line `Range`s."""
+ matches = {}
+ for line in patch_file:
+ line = convert_string(line)
+ match = re.search(r'^\+\+\+\ [^/]+/(.*)', line)
+ if match:
+ filename = match.group(1).rstrip('\r\n\t')
+ match = re.search(r'^@@ -[0-9,]+ \+(\d+)(,(\d+))?', line)
+ if match:
+ start_line = int(match.group(1))
+ line_count = 1
+ if match.group(3):
+ line_count = int(match.group(3))
+ if line_count == 0:
+ line_count = 1
+ if start_line == 0:
+ continue
+ matches.setdefault(filename, []).append(Range(start_line, line_count))
+ return matches
+
+
+def filter_by_extension(dictionary, allowed_extensions):
+ """Delete every key in `dictionary` that doesn't have an allowed extension.
+
+ `allowed_extensions` must be a collection of lowercase file extensions,
+ excluding the period."""
+ allowed_extensions = frozenset(allowed_extensions)
+ for filename in list(dictionary.keys()):
+ base_ext = filename.rsplit('.', 1)
+ if len(base_ext) == 1 and '' in allowed_extensions:
+ continue
+ if len(base_ext) == 1 or base_ext[1].lower() not in allowed_extensions:
+ del dictionary[filename]
+
+
+def filter_symlinks(dictionary):
+ """Delete every key in `dictionary` that is a symlink."""
+ for filename in list(dictionary.keys()):
+ if os.path.islink(filename):
+ del dictionary[filename]
+
+
+def cd_to_toplevel():
+ """Change to the top level of the git repository."""
+ toplevel = run('git', 'rev-parse', '--show-toplevel')
+ os.chdir(toplevel)
+
+
+def create_tree_from_workdir(filenames):
+ """Create a new git tree with the given files from the working directory.
+
+ Returns the object ID (SHA-1) of the created tree."""
+ return create_tree(filenames, '--stdin')
+
+
+def create_tree_from_index(filenames):
+ # Copy the environment, because the files have to be read from the original
+ # index.
+ env = os.environ.copy()
+ def index_contents_generator():
+ for filename in filenames:
+ git_ls_files_cmd = ['git', 'ls-files', '--stage', '-z', '--', filename]
+ git_ls_files = subprocess.Popen(git_ls_files_cmd, env=env,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ stdout = git_ls_files.communicate()[0]
+ yield convert_string(stdout.split(b'\0')[0])
+ return create_tree(index_contents_generator(), '--index-info')
+
+
+def run_clang_format_and_save_to_tree(changed_lines, revision=None,
+ binary='clang-format', style=None):
+ """Run clang-format on each file and save the result to a git tree.
+
+ Returns the object ID (SHA-1) of the created tree."""
+ # Copy the environment when formatting the files in the index, because the
+ # files have to be read from the original index.
+ env = os.environ.copy() if revision == '' else None
+ def iteritems(container):
+ try:
+ return container.iteritems() # Python 2
+ except AttributeError:
+ return container.items() # Python 3
+ def index_info_generator():
+ for filename, line_ranges in iteritems(changed_lines):
+ if revision is not None:
+ if len(revision) > 0:
+ git_metadata_cmd = ['git', 'ls-tree',
+ '%s:%s' % (revision, os.path.dirname(filename)),
+ os.path.basename(filename)]
+ else:
+ git_metadata_cmd = ['git', 'ls-files', '--stage', '--', filename]
+ git_metadata = subprocess.Popen(git_metadata_cmd, env=env,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ stdout = git_metadata.communicate()[0]
+ mode = oct(int(stdout.split()[0], 8))
+ else:
+ mode = oct(os.stat(filename).st_mode)
+ # Adjust python3 octal format so that it matches what git expects
+ if mode.startswith('0o'):
+ mode = '0' + mode[2:]
+ blob_id = clang_format_to_blob(filename, line_ranges,
+ revision=revision,
+ binary=binary,
+ style=style,
+ env=env)
+ yield '%s %s\t%s' % (mode, blob_id, filename)
+ return create_tree(index_info_generator(), '--index-info')
+
+
+def create_tree(input_lines, mode):
+ """Create a tree object from the given input.
+
+ If mode is '--stdin', it must be a list of filenames. If mode is
+ '--index-info' is must be a list of values suitable for "git update-index
+ --index-info", such as "<mode> <SP> <sha1> <TAB> <filename>". Any other mode
+ is invalid."""
+ assert mode in ('--stdin', '--index-info')
+ cmd = ['git', 'update-index', '--add', '-z', mode]
+ with temporary_index_file():
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
+ for line in input_lines:
+ p.stdin.write(to_bytes('%s\0' % line))
+ p.stdin.close()
+ if p.wait() != 0:
+ die('`%s` failed' % ' '.join(cmd))
+ tree_id = run('git', 'write-tree')
+ return tree_id
+
+
+def clang_format_to_blob(filename, line_ranges, revision=None,
+ binary='clang-format', style=None, env=None):
+ """Run clang-format on the given file and save the result to a git blob.
+
+ Runs on the file in `revision` if not None, or on the file in the working
+ directory if `revision` is None. Revision can be set to an empty string to run
+ clang-format on the file in the index.
+
+ Returns the object ID (SHA-1) of the created blob."""
+ clang_format_cmd = [binary]
+ if style:
+ clang_format_cmd.extend(['-style='+style])
+ clang_format_cmd.extend([
+ '-lines=%s:%s' % (start_line, start_line+line_count-1)
+ for start_line, line_count in line_ranges])
+ if revision is not None:
+ clang_format_cmd.extend(['-assume-filename='+filename])
+ git_show_cmd = ['git', 'cat-file', 'blob', '%s:%s' % (revision, filename)]
+ git_show = subprocess.Popen(git_show_cmd, env=env, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ git_show.stdin.close()
+ clang_format_stdin = git_show.stdout
+ else:
+ clang_format_cmd.extend([filename])
+ git_show = None
+ clang_format_stdin = subprocess.PIPE
+ try:
+ clang_format = subprocess.Popen(clang_format_cmd, stdin=clang_format_stdin,
+ stdout=subprocess.PIPE)
+ if clang_format_stdin == subprocess.PIPE:
+ clang_format_stdin = clang_format.stdin
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ die('cannot find executable "%s"' % binary)
+ else:
+ raise
+ clang_format_stdin.close()
+ hash_object_cmd = ['git', 'hash-object', '-w', '--path='+filename, '--stdin']
+ hash_object = subprocess.Popen(hash_object_cmd, stdin=clang_format.stdout,
+ stdout=subprocess.PIPE)
+ clang_format.stdout.close()
+ stdout = hash_object.communicate()[0]
+ if hash_object.returncode != 0:
+ die('`%s` failed' % ' '.join(hash_object_cmd))
+ if clang_format.wait() != 0:
+ die('`%s` failed' % ' '.join(clang_format_cmd))
+ if git_show and git_show.wait() != 0:
+ die('`%s` failed' % ' '.join(git_show_cmd))
+ return convert_string(stdout).rstrip('\r\n')
+
+
+@contextlib.contextmanager
+def temporary_index_file(tree=None):
+ """Context manager for setting GIT_INDEX_FILE to a temporary file and deleting
+ the file afterward."""
+ index_path = create_temporary_index(tree)
+ old_index_path = os.environ.get('GIT_INDEX_FILE')
+ os.environ['GIT_INDEX_FILE'] = index_path
+ try:
+ yield
+ finally:
+ if old_index_path is None:
+ del os.environ['GIT_INDEX_FILE']
+ else:
+ os.environ['GIT_INDEX_FILE'] = old_index_path
+ os.remove(index_path)
+
+
+def create_temporary_index(tree=None):
+ """Create a temporary index file and return the created file's path.
+
+ If `tree` is not None, use that as the tree to read in. Otherwise, an
+ empty index is created."""
+ gitdir = run('git', 'rev-parse', '--git-dir')
+ path = os.path.join(gitdir, temp_index_basename)
+ if tree is None:
+ tree = '--empty'
+ run('git', 'read-tree', '--index-output='+path, tree)
+ return path
+
+
+def print_diff(old_tree, new_tree):
+ """Print the diff between the two trees to stdout."""
+ # We use the porcelain 'diff' and not plumbing 'diff-tree' because the output
+ # is expected to be viewed by the user, and only the former does nice things
+ # like color and pagination.
+ #
+ # We also only print modified files since `new_tree` only contains the files
+ # that were modified, so unmodified files would show as deleted without the
+ # filter.
+ return subprocess.run(['git', 'diff', '--diff-filter=M',
+ '--exit-code', old_tree, new_tree]).returncode
+
+def print_diffstat(old_tree, new_tree):
+ """Print the diffstat between the two trees to stdout."""
+ # We use the porcelain 'diff' and not plumbing 'diff-tree' because the output
+ # is expected to be viewed by the user, and only the former does nice things
+ # like color and pagination.
+ #
+ # We also only print modified files since `new_tree` only contains the files
+ # that were modified, so unmodified files would show as deleted without the
+ # filter.
+ return subprocess.run(['git', 'diff', '--diff-filter=M', '--exit-code',
+ '--stat', old_tree, new_tree]).returncode
+
+def apply_changes(old_tree, new_tree, force=False, patch_mode=False):
+ """Apply the changes in `new_tree` to the working directory.
+
+ Bails if there are local changes in those files and not `force`. If
+ `patch_mode`, runs `git checkout --patch` to select hunks interactively."""
+ changed_files = run('git', 'diff-tree', '--diff-filter=M', '-r', '-z',
+ '--name-only', old_tree,
+ new_tree).rstrip('\0').split('\0')
+ if not force:
+ unstaged_files = run('git', 'diff-files', '--name-status', *changed_files)
+ if unstaged_files:
+ print('The following files would be modified but '
+ 'have unstaged changes:', file=sys.stderr)
+ print(unstaged_files, file=sys.stderr)
+ print('Please commit, stage, or stash them first.', file=sys.stderr)
+ sys.exit(2)
+ if patch_mode:
+ # In patch mode, we could just as well create an index from the new tree
+ # and checkout from that, but then the user will be presented with a
+ # message saying "Discard ... from worktree". Instead, we use the old
+ # tree as the index and checkout from new_tree, which gives the slightly
+ # better message, "Apply ... to index and worktree". This is not quite
+ # right, since it won't be applied to the user's index, but oh well.
+ with temporary_index_file(old_tree):
+ subprocess.run(['git', 'checkout', '--patch', new_tree], check=True)
+ index_tree = old_tree
+ else:
+ with temporary_index_file(new_tree):
+ run('git', 'checkout-index', '-f', '--', *changed_files)
+ return changed_files
+
+
+def run(*args, **kwargs):
+ stdin = kwargs.pop('stdin', '')
+ verbose = kwargs.pop('verbose', True)
+ strip = kwargs.pop('strip', True)
+ for name in kwargs:
+ raise TypeError("run() got an unexpected keyword argument '%s'" % name)
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ stdin=subprocess.PIPE)
+ stdout, stderr = p.communicate(input=stdin)
+
+ stdout = convert_string(stdout)
+ stderr = convert_string(stderr)
+
+ if p.returncode == 0:
+ if stderr:
+ if verbose:
+ print('`%s` printed to stderr:' % ' '.join(args), file=sys.stderr)
+ print(stderr.rstrip(), file=sys.stderr)
+ if strip:
+ stdout = stdout.rstrip('\r\n')
+ return stdout
+ if verbose:
+ print('`%s` returned %s' % (' '.join(args), p.returncode), file=sys.stderr)
+ if stderr:
+ print(stderr.rstrip(), file=sys.stderr)
+ sys.exit(2)
+
+
+def die(message):
+ print('error:', message, file=sys.stderr)
+ sys.exit(2)
+
+
+def to_bytes(str_input):
+ # Encode to UTF-8 to get binary data.
+ if isinstance(str_input, bytes):
+ return str_input
+ return str_input.encode('utf-8')
+
+
+def to_string(bytes_input):
+ if isinstance(bytes_input, str):
+ return bytes_input
+ return bytes_input.encode('utf-8')
+
+
+def convert_string(bytes_input):
+ try:
+ return to_string(bytes_input.decode('utf-8'))
+ except AttributeError: # 'str' object has no attribute 'decode'.
+ return str(bytes_input)
+ except UnicodeError:
+ return str(bytes_input)
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/scripts/run-analyzer.sh b/scripts/run-analyzer.sh
index a410e8b4c..9e53e50dd 100755
--- a/scripts/run-analyzer.sh
+++ b/scripts/run-analyzer.sh
@@ -45,12 +45,12 @@ LLVM_INSTALL_DIR=${LLVM_INSTALL_DIR:-""}
# update-alternatives --install /usr/bin/run-clang-tidy.py run-clang-tidy.py /usr/bin/run-clang-tidy-4.0.py 1
CLANG_TIDY=`which clang-tidy`
-RUN_CLANG_TIDY=`which run-clang-tidy.py`
+RUN_CLANG_TIDY=`which run-clang-tidy`
if [ -z "$RUN_CLANG_TIDY" ] || [ -z "$CLANG_TIDY" ]; then
if [ ! -z "$LLVM_INSTALL_DIR" ]; then
CLANG_TIDY="$LLVM_INSTALL_DIR/bin/clang-tidy"
- RUN_CLANG_TIDY="$LLVM_INSTALL_DIR/share/clang/run-clang-tidy.py"
+ RUN_CLANG_TIDY="$LLVM_INSTALL_DIR/bin/run-clang-tidy"
else
echo "Can't find clang-tidy and/or run-clang-tidy.py in PATH, try setting LLVM_INSTALL_DIR"
exit 1
diff --git a/share/CMakeLists.txt b/share/CMakeLists.txt
index 976111f71..e47bd74f0 100644
--- a/share/CMakeLists.txt
+++ b/share/CMakeLists.txt
@@ -8,22 +8,8 @@ endif()
# below we copy some files that are required to run qbs from the build directory
# emulating the same layout we will have after the installation
-# copy & install dmgbuild
-install(
- PROGRAMS ../src/3rdparty/python/bin/dmgbuild
- DESTINATION "${QBS_LIBEXEC_INSTALL_DIR}"
- )
-set(_DMGBUILD_INSTALL_PATH ${CMAKE_BINARY_DIR}/${QBS_OUTPUT_PREFIX}${QBS_LIBEXEC_INSTALL_DIR}/dmgbuild)
-add_custom_command(
- OUTPUT ${_DMGBUILD_INSTALL_PATH}
- COMMAND ${CMAKE_COMMAND} -E copy
- ${CMAKE_CURRENT_SOURCE_DIR}/../src/3rdparty/python/bin/dmgbuild
- ${CMAKE_BINARY_DIR}/${QBS_OUTPUT_PREFIX}${QBS_LIBEXEC_INSTALL_DIR}
- COMMENT "Copying dmgbuild script"
- )
-
# copy & install python packages
-set(_SITE_PACKAGES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src/3rdparty/python/lib/python2.7/site-packages/)
+set(_SITE_PACKAGES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src/3rdparty/python/lib/python3.9/site-packages/)
file(GLOB_RECURSE _SITE_PACKAGES_RELATIVE RELATIVE "${_SITE_PACKAGES_DIR}" "${_SITE_PACKAGES_DIR}/*.py")
set(_SITE_PACKAGES_SRC ${_SITE_PACKAGES_RELATIVE})
@@ -102,7 +88,6 @@ add_custom_command(
add_custom_target(
BuildQbsResources ALL
DEPENDS
- "${_DMGBUILD_INSTALL_PATH}"
${_SITE_PACKAGES_DST}
${_QBS_RESOURCES_DST}
"${_QMLTYPES_INSTALL_PATH}"
diff --git a/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs b/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs
index 61a9c0b74..0fe81c3cc 100644
--- a/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs
+++ b/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs
@@ -85,6 +85,12 @@ Probe {
&& p.exec(executable, versionArgs.concat(packageNames)) !== 0) {
return;
}
+
+ // protobuf is reserved as qbs module name, which depends on the protobuflib module
+ packageNames = packageNames.map(function(name) {
+ return name === "protobuflib" ? "protobuf" : name;
+ });
+
var args = packageNames;
if (p.exec(executable, args.concat([ '--cflags' ])) === 0) {
stdout = p.readStdOut().trim();
diff --git a/share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs b/share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs
new file mode 100644
index 000000000..5acec6bc0
--- /dev/null
+++ b/share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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.
+**
+****************************************************************************/
+
+import "qbs-pkg-config-probe.js" as PkgConfigProbeConfigure
+
+Probe {
+ // Inputs
+
+ property string _executableFilePath
+ property stringList _extraPaths
+ property stringList _libDirs
+ property bool _staticMode: false
+ property bool _definePrefix: false
+
+ property path _sysroot
+
+ // Output
+ property var packages
+ property var packagesByModuleName
+ property var brokenPackages
+ property stringList qmakePaths
+
+ configure: {
+ var result = PkgConfigProbeConfigure.configure(
+ _executableFilePath,
+ _extraPaths,
+ _libDirs,
+ _staticMode,
+ _definePrefix,
+ _sysroot);
+ packages = result.packages;
+ packagesByModuleName = result.packagesByModuleName;
+ brokenPackages = result.brokenPackages;
+ qmakePaths = result.qmakePaths;
+ found = true;
+ }
+}
diff --git a/share/qbs/imports/qbs/Probes/QmakeProbe.qbs b/share/qbs/imports/qbs/Probes/QmakeProbe.qbs
new file mode 100644
index 000000000..c50c6c851
--- /dev/null
+++ b/share/qbs/imports/qbs/Probes/QmakeProbe.qbs
@@ -0,0 +1,41 @@
+
+/****************************************************************************
+**
+** Copyright (C) 2023 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.
+**
+****************************************************************************/
+
+import "qmake-probe.js" as QmakeProbeConfigure
+
+Probe {
+ property stringList qmakePaths
+ property varList qtInfos
+
+ configure: {
+ qtInfos = QmakeProbeConfigure.configure(qmakePaths);
+ }
+}
diff --git a/share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js b/share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js
new file mode 100644
index 000000000..d382dfb02
--- /dev/null
+++ b/share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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.
+**
+****************************************************************************/
+
+var Environment = require("qbs.Environment");
+var File = require("qbs.File");
+var FileInfo = require("qbs.FileInfo");
+var PkgConfig = require("qbs.PkgConfig");
+var ProviderUtils = require("qbs.ProviderUtils");
+var Process = require("qbs.Process");
+
+function getQmakePaths(pkg) {
+ var packageName = pkg.baseFileName;
+ if (packageName === "QtCore"
+ || packageName === "Qt5Core"
+ || packageName === "Qt6Core") {
+ var binDir = pkg.variables["bindir"] || pkg.variables["host_bins"];
+ if (!binDir) {
+ if (packageName === "QtCore") { // Qt4 does not have host_bins
+ var mocLocation = pkg.variables["moc_location"];
+ if (!mocLocation) {
+ console.warn("No moc_location variable in " + packageName);
+ return;
+ }
+ binDir = FileInfo.path(mocLocation);
+ } else {
+ console.warn("No 'bindir' or 'host_bins' variable in " + packageName);
+ return;
+ }
+ }
+ var suffix = FileInfo.executableSuffix();
+ return [FileInfo.joinPaths(binDir, "qmake" + suffix)];
+ }
+}
+
+function configure(
+ executableFilePath, extraPaths, libDirs, staticMode, definePrefix, sysroot) {
+
+ var result = {};
+ result.packages = [];
+ result.packagesByModuleName = {};
+ result.brokenPackages = [];
+ result.qtInfos = [];
+
+ var options = {};
+ options.libDirs = libDirs;
+ options.sysroot = sysroot;
+ options.definePrefix = definePrefix;
+ if (options.sysroot)
+ options.allowSystemLibraryPaths = true;
+ options.staticMode = staticMode;
+ options.extraPaths = extraPaths;
+ if (options.sysroot && !options.libDirs) {
+ options.libDirs = [
+ options.sysroot + "/usr/lib/pkgconfig",
+ options.sysroot + "/usr/share/pkgconfig"
+ ];
+ }
+ if (!options.libDirs) {
+ // if we have pkg-config/pkgconf installed, let's ask it for its search paths (since
+ // built-in search paths can differ between platforms)
+ if (executableFilePath) {
+ var p = new Process()
+ if (p.exec(executableFilePath, ['pkg-config', '--variable=pc_path']) === 0) {
+ var stdout = p.readStdOut().trim();
+ options.libDirs = stdout ? stdout.split(FileInfo.pathListSeparator()): [];
+ var installDir = FileInfo.path(executableFilePath);
+ options.libDirs = options.libDirs.map(function(path){
+ if (FileInfo.isAbsolutePath(path))
+ return path;
+ return FileInfo.cleanPath(FileInfo.joinPaths(installDir, path));
+ });
+ }
+ }
+ }
+ var pkgConfig = new PkgConfig(options);
+ result.packages = pkgConfig.packages();
+ for (var packageName in result.packages) {
+ var pkg = result.packages[packageName];
+ var moduleName = ProviderUtils.pkgConfigToModuleName(packageName);
+ result.packagesByModuleName[moduleName] = pkg;
+
+ if (packageName.startsWith("Qt")) {
+ if (!sysroot) {
+ var qmakePaths = getQmakePaths(pkg);
+ if (qmakePaths !== undefined)
+ result.qmakePaths = qmakePaths;
+ }
+ }
+ }
+ return result;
+}
diff --git a/share/qbs/imports/qbs/Probes/qmake-probe.js b/share/qbs/imports/qbs/Probes/qmake-probe.js
new file mode 100644
index 000000000..746914e20
--- /dev/null
+++ b/share/qbs/imports/qbs/Probes/qmake-probe.js
@@ -0,0 +1,1256 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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$
+**
+****************************************************************************/
+
+var Environment = require("qbs.Environment");
+var File = require("qbs.File");
+var FileInfo = require("qbs.FileInfo");
+var Host = require("qbs.Host");
+var Process = require("qbs.Process");
+var ProviderUtils = require("qbs.ProviderUtils");
+var TextFile = require("qbs.TextFile");
+var Utilities = require("qbs.Utilities");
+
+function splitNonEmpty(s, c) { return s.split(c).filter(function(e) { return e; }); }
+
+function getQmakeFilePaths(qmakeFilePaths) {
+ if (qmakeFilePaths && qmakeFilePaths.length > 0)
+ return qmakeFilePaths;
+ console.info("Detecting Qt installations...");
+ var filePaths = [];
+ var pathValue = Environment.getEnv("PATH");
+ if (pathValue) {
+ var dirs = splitNonEmpty(pathValue, FileInfo.pathListSeparator());
+ for (var i = 0; i < dirs.length; ++i) {
+ var candidate = FileInfo.joinPaths(dirs[i], "qmake" + FileInfo.executableSuffix());
+ var canonicalCandidate = FileInfo.canonicalPath(candidate);
+ if (!canonicalCandidate || !File.exists(canonicalCandidate))
+ continue;
+ if (FileInfo.completeBaseName(canonicalCandidate) !== "qtchooser")
+ candidate = canonicalCandidate;
+ if (!filePaths.contains(candidate)) {
+ console.info("Found Qt at '" + FileInfo.toNativeSeparators(candidate) + "'.");
+ filePaths.push(candidate);
+ }
+ }
+ }
+ if (filePaths.length === 0) {
+ console.warn("Could not find any qmake executables in PATH. Either make sure a qmake "
+ + "executable is present in PATH or set the moduleProviders.Qt.qmakeFilePaths property "
+ + "to point to a qmake executable.");
+ }
+ return filePaths;
+}
+
+function queryQmake(qmakeFilePath) {
+ var qmakeProcess = new Process;
+ qmakeProcess.exec(qmakeFilePath, ["-query"]);
+ if (qmakeProcess.exitCode() !== 0) {
+ throw "The qmake executable '" + FileInfo.toNativeSeparators(qmakeFilePath)
+ + "' failed with exit code " + qmakeProcess.exitCode() + ".";
+ }
+ var queryResult = {};
+ while (!qmakeProcess.atEnd()) {
+ var line = qmakeProcess.readLine();
+ var index = (line || "").indexOf(":");
+ if (index !== -1)
+ queryResult[line.slice(0, index)] = line.slice(index + 1).trim();
+ }
+ return queryResult;
+}
+
+function pathQueryValue(queryResult, key) {
+ var p = queryResult[key];
+ if (p)
+ return FileInfo.fromNativeSeparators(p);
+}
+
+function readFileContent(filePath) {
+ var f = new TextFile(filePath, TextFile.ReadOnly);
+ var content = f.readAll();
+ f.close();
+ return content;
+}
+
+// TODO: Don't do the split every time...
+function configVariable(configContent, key) {
+ var configContentLines = configContent.split('\n');
+ var regexp = new RegExp("^\\s*" + key + "\\s*\\+{0,1}=(.*)");
+ for (var i = 0; i < configContentLines.length; ++i) {
+ var line = configContentLines[i];
+ var match = regexp.exec(line);
+ if (match)
+ return match[1].trim();
+ }
+}
+
+function configVariableItems(configContent, key) {
+ return splitNonEmpty(configVariable(configContent, key), ' ');
+}
+
+function msvcCompilerVersionForYear(year) {
+ var mapping = {
+ "2005": "14", "2008": "15", "2010": "16", "2012": "17", "2013": "18", "2015": "19",
+ "2017": "19.1", "2019": "19.2"
+ };
+ return mapping[year];
+}
+
+function msvcCompilerVersionFromMkspecName(mkspecName) {
+ return msvcCompilerVersionForYear(mkspecName.slice(msvcPrefix().length));
+}
+
+function addQtBuildVariant(qtProps, buildVariantName) {
+ if (qtProps.qtConfigItems.contains(buildVariantName))
+ qtProps.buildVariant.push(buildVariantName);
+}
+
+function checkForStaticBuild(qtProps) {
+ if (qtProps.qtMajorVersion >= 5)
+ return qtProps.qtConfigItems.contains("static");
+ if (qtProps.frameworkBuild)
+ return false; // there are no Qt4 static frameworks
+ var isWin = qtProps.mkspecName.startsWith("win");
+ var libDir = isWin ? qtProps.binaryPath : qtProps.libraryPath;
+ var coreLibFiles = File.directoryEntries(libDir, File.Files)
+ .filter(function(fp) { return fp.contains("Core"); });
+ if (coreLibFiles.length === 0)
+ throw "Could not determine whether Qt is a static build.";
+ for (var i = 0; i < coreLibFiles.length; ++i) {
+ if (Utilities.isSharedLibrary(coreLibFiles[i]))
+ return false;
+ }
+ return true;
+}
+
+function guessMinimumWindowsVersion(qtProps) {
+ if (qtProps.mkspecName.startsWith("winrt-"))
+ return "10.0";
+ if (!ProviderUtils.isDesktopWindowsQt(qtProps))
+ return "";
+ if (qtProps.qtMajorVersion >= 6)
+ return "10.0";
+ if (qtProps.architecture === "x86_64" || qtProps.architecture === "ia64")
+ return "5.2"
+ var match = qtProps.mkspecName.match(/^win32-msvc(\d+)$/);
+ if (match) {
+ var msvcVersion = match[1];
+ if (msvcVersion < 2012)
+ return "5.0";
+ return "5.1";
+ }
+ return qtProps.qtMajorVersion < 5 ? "5.0" : "5.1";
+}
+
+function fillEntryPointLibs(qtProps, debug) {
+ result = [];
+ var isMinGW = ProviderUtils.isMinGwQt(qtProps);
+
+ // Some Linux distributions rename the qtmain library.
+ var qtMainCandidates = ["qtmain"];
+ if (isMinGW && qtProps.qtMajorVersion === 5)
+ qtMainCandidates.push("qt5main");
+ if (qtProps.qtMajorVersion === 6)
+ qtMainCandidates.push("Qt6EntryPoint");
+
+ for (var i = 0; i < qtMainCandidates.length; ++i) {
+ var baseNameCandidate = qtMainCandidates[i];
+ var qtmain = qtProps.libraryPath + '/';
+ if (isMinGW)
+ qtmain += "lib";
+ qtmain += baseNameCandidate + qtProps.qtLibInfix;
+ if (debug && ProviderUtils.qtNeedsDSuffix(qtProps))
+ qtmain += 'd';
+ if (isMinGW) {
+ qtmain += ".a";
+ } else {
+ qtmain += ".lib";
+ if (Utilities.versionCompare(qtProps.qtVersion, "5.4.0") >= 0)
+ result.push("Shell32.lib");
+ }
+ if (File.exists(qtmain)) {
+ result.push(qtmain);
+ break;
+ }
+ }
+ if (result.length === 0) {
+ console.warn("Could not find the qtmain library at '"
+ + FileInfo.toNativeSeparators(qtProps.libraryPath)
+ + "'. You will not be able to link Qt applications.");
+ }
+ return result;
+}
+
+function getQtProperties(qmakeFilePath) {
+ var queryResult = queryQmake(qmakeFilePath);
+ var qtProps = {};
+ qtProps.installPrefixPath = pathQueryValue(queryResult, "QT_INSTALL_PREFIX");
+ qtProps.documentationPath = pathQueryValue(queryResult, "QT_INSTALL_DOCS");
+ qtProps.includePath = pathQueryValue(queryResult, "QT_INSTALL_HEADERS");
+ qtProps.libraryPath = pathQueryValue(queryResult, "QT_INSTALL_LIBS");
+ qtProps.hostLibraryPath = pathQueryValue(queryResult, "QT_HOST_LIBS");
+ qtProps.binaryPath = pathQueryValue(queryResult, "QT_HOST_BINS")
+ || pathQueryValue(queryResult, "QT_INSTALL_BINS");
+ qtProps.installPath = pathQueryValue(queryResult, "QT_INSTALL_BINS");
+ qtProps.documentationPath = pathQueryValue(queryResult, "QT_INSTALL_DOCS");
+ qtProps.pluginPath = pathQueryValue(queryResult, "QT_INSTALL_PLUGINS");
+ qtProps.qmlPath = pathQueryValue(queryResult, "QT_INSTALL_QML");
+ qtProps.qmlImportPath = pathQueryValue(queryResult, "QT_INSTALL_IMPORTS");
+ qtProps.qtVersion = queryResult.QT_VERSION;
+
+ var mkspecsBaseSrcPath;
+ if (Utilities.versionCompare(qtProps.qtVersion, "5") >= 0) {
+ qtProps.mkspecBasePath = FileInfo.joinPaths(pathQueryValue(queryResult, "QT_HOST_DATA"),
+ "mkspecs");
+ mkspecsBaseSrcPath = FileInfo.joinPaths(pathQueryValue(queryResult, "QT_HOST_DATA/src"),
+ "mkspecs");
+ } else {
+ qtProps.mkspecBasePath = FileInfo.joinPaths
+ (pathQueryValue(queryResult, "QT_INSTALL_DATA"), "mkspecs");
+ }
+
+ if (Utilities.versionCompare(qtProps.qtVersion, "6") >= 0) {
+ qtProps.libExecPath = pathQueryValue(queryResult, "QT_HOST_LIBEXECS")
+ || pathQueryValue(queryResult, "QT_INSTALL_LIBEXECS");
+ }
+
+ // QML tools were only moved in Qt 6.2.
+ qtProps.qmlLibExecPath = Utilities.versionCompare(qtProps.qtVersion, "6.2") >= 0
+ ? qtProps.libExecPath : qtProps.binaryPath;
+
+ // qhelpgenerator was only moved in Qt 6.3.
+ qtProps.helpGeneratorLibExecPath = Utilities.versionCompare(qtProps.qtVersion, "6.3") >= 0
+ ? qtProps.libExecPath : qtProps.binaryPath;
+
+ if (!File.exists(qtProps.mkspecBasePath))
+ throw "Cannot extract the mkspecs directory.";
+
+ var qconfigContent = readFileContent(FileInfo.joinPaths(qtProps.mkspecBasePath,
+ "qconfig.pri"));
+ qtProps.qtMajorVersion = parseInt(configVariable(qconfigContent, "QT_MAJOR_VERSION"));
+ qtProps.qtMinorVersion = parseInt(configVariable(qconfigContent, "QT_MINOR_VERSION"));
+ qtProps.qtPatchVersion = parseInt(configVariable(qconfigContent, "QT_PATCH_VERSION"));
+ qtProps.qtNameSpace = configVariable(qconfigContent, "QT_NAMESPACE");
+ qtProps.qtLibInfix = configVariable(qconfigContent, "QT_LIBINFIX") || "";
+ qtProps.architecture = configVariable(qconfigContent, "QT_TARGET_ARCH")
+ || configVariable(qconfigContent, "QT_ARCH") || "x86";
+ qtProps.configItems = configVariableItems(qconfigContent, "CONFIG");
+ qtProps.qtConfigItems = configVariableItems(qconfigContent, "QT_CONFIG");
+
+ // retrieve the mkspec
+ if (qtProps.qtMajorVersion >= 5) {
+ qtProps.mkspecName = queryResult.QMAKE_XSPEC;
+ qtProps.mkspecPath = FileInfo.joinPaths(qtProps.mkspecBasePath, qtProps.mkspecName);
+ if (mkspecsBaseSrcPath && !File.exists(qtProps.mkspecPath))
+ qtProps.mkspecPath = FileInfo.joinPaths(mkspecsBaseSrcPath, qtProps.mkspecName);
+ } else {
+ if (Host.os().contains("windows")) {
+ var baseDirPath = FileInfo.joinPaths(qtProps.mkspecBasePath, "default");
+ var fileContent = readFileContent(FileInfo.joinPaths(baseDirPath, "qmake.conf"));
+ qtProps.mkspecPath = configVariable(fileContent, "QMAKESPEC_ORIGINAL");
+ if (!File.exists(qtProps.mkspecPath)) {
+ // Work around QTBUG-28792.
+ // The value of QMAKESPEC_ORIGINAL is wrong for MinGW packages. Y u h8 me?
+ var match = fileContent.exec(/\binclude\(([^)]+)\/qmake\.conf\)/m);
+ if (match) {
+ qtProps.mkspecPath = FileInfo.cleanPath(FileInfo.joinPaths(
+ baseDirPath, match[1]));
+ }
+ }
+ } else {
+ qtProps.mkspecPath = FileInfo.canonicalPath(
+ FileInfo.joinPaths(qtProps.mkspecBasePath, "default"));
+ }
+
+ // E.g. in qmake.conf for Qt 4.8/mingw we find this gem:
+ // QMAKESPEC_ORIGINAL=C:\\Qt\\Qt\\4.8\\mingw482\\mkspecs\\win32-g++
+ qtProps.mkspecPath = FileInfo.cleanPath(qtProps.mkspecPath);
+
+ qtProps.mkspecName = qtProps.mkspecPath;
+ var idx = qtProps.mkspecName.lastIndexOf('/');
+ if (idx !== -1)
+ qtProps.mkspecName = qtProps.mkspecName.slice(idx + 1);
+ }
+ if (!File.exists(qtProps.mkspecPath))
+ throw "mkspec '" + FileInfo.toNativeSeparators(qtProps.mkspecPath) + "' does not exist";
+
+ // Starting with qt 5.14, android sdk provides multi-abi
+ if (Utilities.versionCompare(qtProps.qtVersion, "5.14.0") >= 0
+ && qtProps.mkspecPath.contains("android")) {
+ var qdeviceContent = readFileContent(FileInfo.joinPaths(qtProps.mkspecBasePath,
+ "qdevice.pri"));
+ qtProps.androidAbis = configVariable(qdeviceContent, "DEFAULT_ANDROID_ABIS").split(' ');
+ }
+
+ // determine MSVC version
+ if (ProviderUtils.isMsvcQt(qtProps)) {
+ var msvcMajor = configVariable(qconfigContent, "QT_MSVC_MAJOR_VERSION");
+ var msvcMinor = configVariable(qconfigContent, "QT_MSVC_MINOR_VERSION");
+ var msvcPatch = configVariable(qconfigContent, "QT_MSVC_PATCH_VERSION");
+ if (msvcMajor && msvcMinor && msvcPatch)
+ qtProps.msvcVersion = msvcMajor + "." + msvcMinor + "." + msvcPatch;
+ else
+ qtProps.msvcVersion = msvcCompilerVersionFromMkspecName(qtProps.mkspecName);
+ }
+
+ // determine whether we have a framework build
+ qtProps.frameworkBuild = qtProps.mkspecPath.contains("macx")
+ && qtProps.configItems.contains("qt_framework");
+
+ // determine whether Qt is built with debug, release or both
+ qtProps.buildVariant = [];
+ addQtBuildVariant(qtProps, "debug");
+ addQtBuildVariant(qtProps, "release");
+
+ qtProps.staticBuild = checkForStaticBuild(qtProps);
+
+ // determine whether user apps require C++11
+ if (qtProps.qtConfigItems.contains("c++11") && qtProps.staticBuild)
+ qtProps.configItems.push("c++11");
+
+ // Set the minimum operating system versions appropriate for this Qt version
+ qtProps.windowsVersion = guessMinimumWindowsVersion(qtProps);
+ if (qtProps.windowsVersion) { // Is target OS Windows?
+ if (qtProps.buildVariant.contains("debug"))
+ qtProps.entryPointLibsDebug = fillEntryPointLibs(qtProps, true);
+ if (qtProps.buildVariant.contains("release"))
+ qtProps.entryPointLibsRelease = fillEntryPointLibs(qtProps, false);
+ } else if (qtProps.mkspecPath.contains("macx")) {
+ if (qtProps.qtMajorVersion >= 5) {
+ // Since Qt 6.7.1, QMAKE_MACOSX|IOS_DEPLOYMENT_TARGET is no longer present in
+ // qmake.conf. But it is also present in qconfig.pri, so first try to read it from there
+ qtProps.macosVersion = configVariable(qconfigContent, "QMAKE_MACOSX_DEPLOYMENT_TARGET");
+ qtProps.iosVersion = configVariable(qconfigContent, "QMAKE_IOS_DEPLOYMENT_TARGET");
+
+ // Next, we override the value from qmake.conf, if present there
+ // Note, that TVOS/WATCHOS variables are only present in qmake.conf (as of Qt 6.7.1)
+ var lines = getFileContentsRecursively(FileInfo.joinPaths(qtProps.mkspecPath,
+ "qmake.conf"));
+ for (var i = 0; i < lines.length; ++i) {
+ var line = lines[i].trim();
+ match = line.match
+ (/^QMAKE_(MACOSX|IOS|TVOS|WATCHOS)_DEPLOYMENT_TARGET\s*=\s*(.*)\s*$/);
+ if (match) {
+ var platform = match[1];
+ var version = match[2];
+ if (platform === "MACOSX")
+ qtProps.macosVersion = version;
+ else if (platform === "IOS")
+ qtProps.iosVersion = version;
+ else if (platform === "TVOS")
+ qtProps.tvosVersion = version;
+ else if (platform === "WATCHOS")
+ qtProps.watchosVersion = version;
+ }
+ }
+ var isMac = qtProps.mkspecName !== "macx-ios-clang"
+ && qtProps.mkspecName !== "macx-tvos-clang"
+ && qtProps.mkspecName !== "macx-watchos-clang";
+ if (isMac) {
+ // Qt 5.0.x placed the minimum version in a different file
+ if (!qtProps.macosVersion)
+ qtProps.macosVersion = "10.6";
+
+ // If we're using C++11 with libc++, make sure the deployment target is >= 10.7
+ if (Utilities.versionCompare(qtProps.macosVersion, "10, 7") < 0
+ && qtProps.qtConfigItems.contains("c++11")) {
+ qtProps.macosVersion = "10.7";
+ }
+ }
+ } else if (qtProps.qtMajorVersion === 4 && qtProps.qtMinorVersion >= 6) {
+ var qconfigDir = qtProps.frameworkBuild
+ ? FileInfo.joinPaths(qtProps.libraryPath, "QtCore.framework", "Headers")
+ : FileInfo.joinPaths(qtProps.includePath, "Qt");
+ try {
+ var qconfig = new TextFile(FileInfo.joinPaths(qconfigDir, "qconfig.h"),
+ TextFile.ReadOnly);
+ var qtCocoaBuild = false;
+ var ok = true;
+ do {
+ line = qconfig.readLine();
+ if (line.match(/\s*#define\s+QT_MAC_USE_COCOA\s+1\s*/)) {
+ qtCocoaBuild = true;
+ break;
+ }
+ } while (!qconfig.atEof());
+ qtProps.macosVersion = qtCocoaBuild ? "10.5" : "10.4";
+ }
+ catch (e) {}
+ finally {
+ if (qconfig)
+ qconfig.close();
+ }
+ if (!qtProps.macosVersion) {
+ throw "Could not determine whether Qt is using Cocoa or Carbon from '"
+ + FileInfo.toNativeSeparators(qconfig.filePath()) + "'.";
+ }
+ }
+ } else if (qtProps.mkspecPath.contains("android")) {
+ if (qtProps.qtMajorVersion >= 5)
+ qtProps.androidVersion = "2.3";
+ else if (qtProps.qtMajorVersion === 4 && qtProps.qtMinorVersion >= 8)
+ qtProps.androidVersion = "1.6"; // Necessitas
+ }
+ return qtProps;
+}
+
+function makePluginData() {
+ var pluginData = {};
+ pluginData.type = undefined;
+ pluginData.className = undefined;
+ pluginData.autoLoad = true;
+ pluginData["extends"] = [];
+ return pluginData;
+}
+
+function makeQtModuleInfo(name, qbsName, deps) {
+ var moduleInfo = {};
+ moduleInfo.name = name; // As in the path to the headers and ".name" in the pri files.
+ if (moduleInfo.name === undefined)
+ moduleInfo.name = "";
+ moduleInfo.qbsName = qbsName; // Lower-case version without "qt" prefix.
+ moduleInfo.dependencies = deps || []; // qbs names.
+ if (moduleInfo.qbsName && moduleInfo.qbsName !== "core"
+ && !moduleInfo.dependencies.contains("core")) {
+ moduleInfo.dependencies.unshift("core");
+ }
+ moduleInfo.isPrivate = qbsName && qbsName.endsWith("-private");
+ moduleInfo.hasLibrary = !moduleInfo.isPrivate;
+ moduleInfo.isStaticLibrary = false;
+ moduleInfo.isPlugin = false;
+ moduleInfo.mustExist = true;
+ moduleInfo.modulePrefix = ""; // empty value means "Qt".
+ moduleInfo.version = undefined;
+ moduleInfo.includePaths = [];
+ moduleInfo.compilerDefines = [];
+ moduleInfo.staticLibrariesDebug = [];
+ moduleInfo.staticLibrariesRelease = [];
+ moduleInfo.dynamicLibrariesDebug = [];
+ moduleInfo.dynamicLibrariesRelease = [];
+ moduleInfo.linkerFlagsDebug = [];
+ moduleInfo.linkerFlagsRelease = [];
+ moduleInfo.libFilePathDebug = undefined;
+ moduleInfo.libFilePathRelease = undefined;
+ moduleInfo.frameworksDebug = [];
+ moduleInfo.frameworksRelease = [];
+ moduleInfo.frameworkPathsDebug = [];
+ moduleInfo.frameworkPathsRelease = [];
+ moduleInfo.libraryPaths = [];
+ moduleInfo.libDir = "";
+ moduleInfo.config = [];
+ moduleInfo.supportedPluginTypes = [];
+ moduleInfo.pluginData = makePluginData();
+ return moduleInfo;
+}
+
+function frameworkHeadersPath(qtModuleInfo, qtProps) {
+ return FileInfo.joinPaths(qtProps.libraryPath, qtModuleInfo.name + ".framework", "Headers");
+}
+
+function qt4ModuleIncludePaths(qtModuleInfo, qtProps) {
+ var paths = [];
+ if (ProviderUtils.qtIsFramework(qtModuleInfo, qtProps))
+ paths.push(frameworkHeadersPath(qtModuleInfo, qtProps));
+ else
+ paths.push(qtProps.includePath, FileInfo.joinPaths(qtProps.includePath, qtModuleInfo.name));
+ return paths;
+}
+
+// We erroneously called the "testlib" module "test" for quite a while. Let's not punish users
+// for that.
+function addTestModule(modules) {
+ var testModule = makeQtModuleInfo("QtTest", "test", ["testlib"]);
+ testModule.hasLibrary = false;
+ modules.push(testModule);
+}
+
+// See above.
+function addDesignerComponentsModule(modules) {
+ var module = makeQtModuleInfo("QtDesignerComponents", "designercomponents",
+ ["designercomponents-private"]);
+ module.hasLibrary = false;
+ modules.push(module);
+}
+
+function guessLibraryFilePath(prlFilePath, libDir, qtProps) {
+ var baseName = FileInfo.baseName(prlFilePath);
+ var prefixCandidates = ["", "lib"];
+ var suffixCandidates = ["so." + qtProps.qtVersion, "so", "a", "lib", "dll.a"];
+ for (var i = 0; i < prefixCandidates.length; ++i) {
+ var prefix = prefixCandidates[i];
+ for (var j = 0; j < suffixCandidates.length; ++j) {
+ var suffix = suffixCandidates[j];
+ var candidate = FileInfo.joinPaths(libDir, prefix + baseName + '.' + suffix);
+ if (File.exists(candidate))
+ return candidate;
+ }
+ }
+}
+
+function doReplaceQtLibNamesWithFilePath(namePathMap, libList) {
+ for (var i = 0; i < libList.length; ++i) {
+ var lib = libList[i];
+ var path = namePathMap[lib];
+ if (path)
+ libList[i] = path;
+ }
+}
+
+function replaceQtLibNamesWithFilePath(modules, qtProps) {
+ // We don't want to add the libraries for Qt modules via "-l", because of the
+ // danger that a wrong one will be picked up, e.g. from /usr/lib. Instead,
+ // we pull them in using the full file path.
+ var linkerNamesToFilePathsDebug = {};
+ var linkerNamesToFilePathsRelease = {};
+ for (var i = 0; i < modules.length; ++i) {
+ var m = modules[i];
+ linkerNamesToFilePathsDebug[
+ ProviderUtils.qtLibNameForLinker(m, qtProps, true)] = m.libFilePathDebug;
+ linkerNamesToFilePathsRelease[
+ ProviderUtils.qtLibNameForLinker(m, qtProps, false)] = m.libFilePathRelease;
+ }
+ for (i = 0; i < modules.length; ++i) {
+ var module = modules[i];
+ doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsDebug, module.dynamicLibrariesDebug);
+ doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsDebug, module.staticLibrariesDebug);
+ doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsRelease,
+ module.dynamicLibrariesRelease);
+ doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsRelease,
+ module.staticLibrariesRelease);
+ }
+}
+
+function doSetupLibraries(modInfo, qtProps, debugBuild, nonExistingPrlFiles, androidAbi) {
+ if (!modInfo.hasLibrary)
+ return; // Can happen for Qt4 convenience modules, like "widgets".
+
+ if (debugBuild) {
+ if (!qtProps.buildVariant.contains("debug"))
+ return;
+ var modulesNeverBuiltAsDebug = ["bootstrap", "qmldevtools"];
+ for (var i = 0; i < modulesNeverBuiltAsDebug.length; ++i) {
+ var m = modulesNeverBuiltAsDebug[i];
+ if (modInfo.qbsName === m || modInfo.qbsName === m + "-private")
+ return;
+ }
+ } else if (!qtProps.buildVariant.contains("release")) {
+ return;
+ }
+
+ var libs = modInfo.isStaticLibrary
+ ? (debugBuild ? modInfo.staticLibrariesDebug : modInfo.staticLibrariesRelease)
+ : (debugBuild ? modInfo.dynamicLibrariesDebug : modInfo.dynamicLibrariesRelease);
+ var frameworks = debugBuild ? modInfo.frameworksDebug : modInfo.frameworksRelease;
+ var frameworkPaths = debugBuild ? modInfo.frameworkPathsDebug : modInfo.frameworkPathsRelease;
+ var flags = debugBuild ? modInfo.linkerFlagsDebug : modInfo.linkerFlagsRelease;
+ var libFilePath;
+
+ if (qtProps.mkspecName.contains("ios") && modInfo.isStaticLibrary) {
+ libs.push("z", "m");
+ if (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 8) {
+ var platformSupportModule = makeQtModuleInfo("QtPlatformSupport", "platformsupport");
+ libs.push(ProviderUtils.qtLibNameForLinker(platformSupportModule, qtProps, debugBuild));
+ }
+ if (modInfo.name === "qios") {
+ flags.push("-force_load", FileInfo.joinPaths(
+ qtProps.pluginPath, "platforms",
+ ProviderUtils.qtLibBaseName(
+ modInfo, "libqios", debugBuild, qtProps) + ".a"));
+ }
+ }
+ var prlFilePath = modInfo.isPlugin
+ ? FileInfo.joinPaths(qtProps.pluginPath, modInfo.pluginData.type)
+ : (modInfo.libDir ? modInfo.libDir : qtProps.libraryPath);
+ var libDir = prlFilePath;
+ if (ProviderUtils.qtIsFramework(modInfo, qtProps)) {
+ prlFilePath = FileInfo.joinPaths(
+ prlFilePath,
+ ProviderUtils.qtLibraryBaseName(modInfo, qtProps, false) + ".framework");
+ libDir = prlFilePath;
+ if (Utilities.versionCompare(qtProps.qtVersion, "5.14") >= 0)
+ prlFilePath = FileInfo.joinPaths(prlFilePath, "Resources");
+ }
+ var baseName = ProviderUtils.qtLibraryBaseName(modInfo, qtProps, debugBuild);
+ if (!qtProps.mkspecName.startsWith("win") && !ProviderUtils.qtIsFramework(modInfo, qtProps))
+ baseName = "lib" + baseName;
+ prlFilePath = FileInfo.joinPaths(prlFilePath, baseName);
+ var isNonStaticQt4OnWindows = qtProps.mkspecName.startsWith("win")
+ && !modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5;
+ if (isNonStaticQt4OnWindows)
+ prlFilePath = prlFilePath.slice(0, prlFilePath.length - 1); // The prl file base name does *not* contain the version number...
+ // qt for android versions 6.0 and 6.1 don't have the architecture suffix in the prl file
+ if (androidAbi.length > 0
+ && modInfo.name !== "QtBootstrap"
+ && (modInfo.name !== "QtQmlDevTools" || modInfo.name === "QtQmlDevTools"
+ && Utilities.versionCompare(qtProps.qtVersion, "6.2") >= 0)
+ && (Utilities.versionCompare(qtProps.qtVersion, "6.0") < 0
+ || Utilities.versionCompare(qtProps.qtVersion, "6.2") >= 0)) {
+ prlFilePath += "_";
+ prlFilePath += androidAbi;
+ }
+
+ prlFilePath += ".prl";
+
+ try {
+ var prlFile = new TextFile(prlFilePath, TextFile.ReadOnly);
+ while (!prlFile.atEof()) {
+ var line = prlFile.readLine().trim();
+ var equalsOffset = line.indexOf('=');
+ if (equalsOffset === -1)
+ continue;
+ if (line.startsWith("QMAKE_PRL_TARGET")) {
+ var isMingw = qtProps.mkspecName.startsWith("win")
+ && qtProps.mkspecName.contains("g++");
+ var isQtVersionBefore56 = qtProps.qtMajorVersion < 5
+ || (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 6);
+
+ // QMAKE_PRL_TARGET has a "lib" prefix, except for mingw.
+ // Of course, the exception has an exception too: For static libs, mingw *does*
+ // have the "lib" prefix.
+ var libFileName = "";
+ if (isQtVersionBefore56 && qtProps.qtMajorVersion === 5 && isMingw
+ && !modInfo.isStaticLibrary) {
+ libFileName += "lib";
+ }
+
+ libFileName += line.slice(equalsOffset + 1).trim();
+ if (isNonStaticQt4OnWindows)
+ libFileName += 4; // This is *not* part of QMAKE_PRL_TARGET...
+ if (isQtVersionBefore56) {
+ if (qtProps.mkspecName.contains("msvc")) {
+ libFileName += ".lib";
+ } else if (isMingw) {
+ libFileName += ".a";
+ if (!File.exists(FileInfo.joinPaths(libDir, libFileName)))
+ libFileName = libFileName.slice(0, -2) + ".dll";
+ }
+ }
+ libFilePath = FileInfo.joinPaths(libDir, libFileName);
+ continue;
+ }
+ if (line.startsWith("QMAKE_PRL_CONFIG")) {
+ modInfo.config = splitNonEmpty(line.slice(equalsOffset + 1).trim(), ' ');
+ continue;
+ }
+ if (!line.startsWith("QMAKE_PRL_LIBS ="))
+ continue;
+
+ var parts = extractPaths(line.slice(equalsOffset + 1).trim(), prlFilePath);
+ for (i = 0; i < parts.length; ++i) {
+ var part = parts[i];
+ part = part.replace("$$[QT_INSTALL_LIBS]", qtProps.libraryPath);
+ part = part.replace("$$[QT_INSTALL_PLUGINS]", qtProps.pluginPath);
+ part = part.replace("$$[QT_INSTALL_PREFIX]", qtProps.installPrefixPath);
+ if (part.startsWith("-l")) {
+ libs.push(part.slice(2));
+ } else if (part.startsWith("-L")) {
+ modInfo.libraryPaths.push(part.slice(2));
+ } else if (part.startsWith("-F")) {
+ frameworkPaths.push(part.slice(2));
+ } else if (part === "-framework") {
+ if (++i < parts.length)
+ frameworks.push(parts[i]);
+ } else if (part === "-pthread") {
+ // prl files for android have QMAKE_PRL_LIBS = -llog -pthread but the pthread
+ // functionality is included in libc.
+ if (androidAbi.length === 0)
+ libs.push("pthread");
+ } else if (part.startsWith('-')) { // Some other option
+ console.debug("QMAKE_PRL_LIBS contains non-library option '" + part
+ + "' in file '" + prlFilePath + "'");
+ flags.push(part);
+ } else if (part.startsWith("/LIBPATH:")) {
+ libraryPaths.push(part.slice(9).replace(/\\/g, '/'));
+ } else { // Assume it's a file path/name.
+ libs.push(part.replace(/\\/g, '/'));
+ }
+ }
+ }
+ } catch (e) {
+ // qt_ext_lib_extX.pri (usually) don't have a corresponding prl file.
+ // So the pri file variable QMAKE_LIBS_LIBX points to the library
+ if (modInfo.isExternal) {
+ libFilePath = debugBuild ? modInfo.staticLibrariesDebug[0] :
+ modInfo.staticLibrariesRelease[0];
+ }
+ if (!libFilePath || !File.exists(libFilePath))
+ libFilePath = guessLibraryFilePath(prlFilePath, libDir, qtProps);
+ if (nonExistingPrlFiles.contains(prlFilePath))
+ return;
+ nonExistingPrlFiles.push(prlFilePath);
+ if (modInfo.mustExist) {
+ console.warn("Could not open prl file '"
+ + FileInfo.toNativeSeparators(prlFilePath) + "' for module '"
+ + modInfo.name
+ + "' (" + e + "), and failed to deduce the library file path. "
+ + " This module will likely not be usable by qbs.");
+ }
+ }
+ finally {
+ if (prlFile)
+ prlFile.close();
+ }
+
+ if (debugBuild)
+ modInfo.libFilePathDebug = libFilePath;
+ else
+ modInfo.libFilePathRelease = libFilePath;
+}
+
+function setupLibraries(qtModuleInfo, qtProps, nonExistingPrlFiles, androidAbi) {
+ doSetupLibraries(qtModuleInfo, qtProps, true, nonExistingPrlFiles, androidAbi);
+ doSetupLibraries(qtModuleInfo, qtProps, false, nonExistingPrlFiles, androidAbi);
+}
+
+function allQt4Modules(qtProps) {
+ // as per http://doc.qt.io/qt-4.8/modules.html + private stuff.
+ var modules = [];
+
+ var core = makeQtModuleInfo("QtCore", "core");
+ core.compilerDefines.push("QT_CORE_LIB");
+ if (qtProps.qtNameSpace)
+ core.compilerDefines.push("QT_NAMESPACE=" + qtProps.qtNameSpace);
+ modules.push(core,
+ makeQtModuleInfo("QtCore", "core-private", ["core"]),
+ makeQtModuleInfo("QtGui", "gui"),
+ makeQtModuleInfo("QtGui", "gui-private", ["gui"]),
+ makeQtModuleInfo("QtMultimedia", "multimedia", ["gui", "network"]),
+ makeQtModuleInfo("QtMultimedia", "multimedia-private", ["multimedia"]),
+ makeQtModuleInfo("QtNetwork", "network"),
+ makeQtModuleInfo("QtNetwork", "network-private", ["network"]),
+ makeQtModuleInfo("QtOpenGL", "opengl", ["gui"]),
+ makeQtModuleInfo("QtOpenGL", "opengl-private", ["opengl"]),
+ makeQtModuleInfo("QtOpenVG", "openvg", ["gui"]),
+ makeQtModuleInfo("QtScript", "script"),
+ makeQtModuleInfo("QtScript", "script-private", ["script"]),
+ makeQtModuleInfo("QtScriptTools", "scripttools", ["script", "gui"]),
+ makeQtModuleInfo("QtScriptTools", "scripttools-private", ["scripttools"]),
+ makeQtModuleInfo("QtSql", "sql"),
+ makeQtModuleInfo("QtSql", "sql-private", ["sql"]),
+ makeQtModuleInfo("QtSvg", "svg", ["gui"]),
+ makeQtModuleInfo("QtSvg", "svg-private", ["svg"]),
+ makeQtModuleInfo("QtWebKit", "webkit", ["gui", "network"]),
+ makeQtModuleInfo("QtWebKit", "webkit-private", ["webkit"]),
+ makeQtModuleInfo("QtXml", "xml"),
+ makeQtModuleInfo("QtXml", "xml-private", ["xml"]),
+ makeQtModuleInfo("QtXmlPatterns", "xmlpatterns", ["network"]),
+ makeQtModuleInfo("QtXmlPatterns", "xmlpatterns-private", ["xmlpatterns"]),
+ makeQtModuleInfo("QtDeclarative", "declarative", ["gui", "script"]),
+ makeQtModuleInfo("QtDeclarative", "declarative-private", ["declarative"]),
+ makeQtModuleInfo("QtDesigner", "designer", ["gui", "xml"]),
+ makeQtModuleInfo("QtDesigner", "designer-private", ["designer"]),
+ makeQtModuleInfo("QtUiTools", "uitools"),
+ makeQtModuleInfo("QtUiTools", "uitools-private", ["uitools"]),
+ makeQtModuleInfo("QtHelp", "help", ["network", "sql"]),
+ makeQtModuleInfo("QtHelp", "help-private", ["help"]),
+ makeQtModuleInfo("QtTest", "testlib"),
+ makeQtModuleInfo("QtTest", "testlib-private", ["testlib"]));
+ if (qtProps.mkspecName.startsWith("win")) {
+ var axcontainer = makeQtModuleInfo("QAxContainer", "axcontainer");
+ axcontainer.modulePrefix = "Q";
+ axcontainer.isStaticLibrary = true;
+ axcontainer.includePaths.push(FileInfo.joinPaths(qtProps.includePath, "ActiveQt"));
+ modules.push(axcontainer);
+
+ var axserver = makeQtModuleInfo("QAxServer", "axserver");
+ axserver.modulePrefix = "Q";
+ axserver.isStaticLibrary = true;
+ axserver.compilerDefines.push("QAXSERVER");
+ axserver.includePaths.push(FileInfo.joinPaths(qtProps.includePath, "ActiveQt"));
+ modules.push(axserver);
+ } else {
+ modules.push(makeQtModuleInfo("QtDBus", "dbus"));
+ modules.push(makeQtModuleInfo("QtDBus", "dbus-private", ["dbus"]));
+ }
+
+ var designerComponentsPrivate = makeQtModuleInfo(
+ "QtDesignerComponents", "designercomponents-private",
+ ["gui-private", "designer-private"]);
+ designerComponentsPrivate.hasLibrary = true;
+ modules.push(designerComponentsPrivate);
+
+ var phonon = makeQtModuleInfo("Phonon", "phonon");
+ phonon.includePaths = qt4ModuleIncludePaths(phonon, qtProps);
+ modules.push(phonon);
+
+ // Set up include paths that haven't been set up before this point.
+ for (i = 0; i < modules.length; ++i) {
+ var module = modules[i];
+ if (module.includePaths.length > 0)
+ continue;
+ module.includePaths = qt4ModuleIncludePaths(module, qtProps);
+ }
+
+ // Set up compiler defines haven't been set up before this point.
+ for (i = 0; i < modules.length; ++i) {
+ module = modules[i];
+ if (module.compilerDefines.length > 0)
+ continue;
+ module.compilerDefines.push("QT_" + module.qbsName.toUpperCase() + "_LIB");
+ }
+
+ // These are for the convenience of project file authors. It allows them
+ // to add a dependency to e.g. "Qt.widgets" without a version check.
+ var virtualModule = makeQtModuleInfo(undefined, "widgets", ["core", "gui"]);
+ virtualModule.hasLibrary = false;
+ modules.push(virtualModule);
+ virtualModule = makeQtModuleInfo(undefined, "quick", ["declarative"]);
+ virtualModule.hasLibrary = false;
+ modules.push(virtualModule);
+ virtualModule = makeQtModuleInfo(undefined, "concurrent");
+ virtualModule.hasLibrary = false;
+ modules.push(virtualModule);
+ virtualModule = makeQtModuleInfo(undefined, "printsupport", ["core", "gui"]);
+ virtualModule.hasLibrary = false;
+ modules.push(virtualModule);
+
+ addTestModule(modules);
+ addDesignerComponentsModule(modules);
+
+ var modulesThatCanBeDisabled = [
+ "xmlpatterns", "multimedia", "phonon", "svg", "webkit", "script", "scripttools",
+ "declarative", "gui", "dbus", "opengl", "openvg"];
+ var nonExistingPrlFiles = [];
+ for (i = 0; i < modules.length; ++i) {
+ module = modules[i];
+ var name = module.qbsName;
+ var privateIndex = name.indexOf("-private");
+ if (privateIndex !== -1)
+ name = name.slice(0, privateIndex);
+ if (modulesThatCanBeDisabled.contains(name))
+ module.mustExist = false;
+ if (qtProps.staticBuild)
+ module.isStaticLibrary = true;
+ setupLibraries(module, qtProps, nonExistingPrlFiles, "");
+ }
+ replaceQtLibNamesWithFilePath(modules, qtProps);
+
+ return modules;
+}
+
+function getFileContentsRecursively(filePath) {
+ var file = new TextFile(filePath, TextFile.ReadOnly);
+ var lines = splitNonEmpty(file.readAll(), '\n');
+ for (var i = 0; i < lines.length; ++i) {
+ var includeString = "include(";
+ var line = lines[i].trim();
+ if (!line.startsWith(includeString))
+ continue;
+ var offset = includeString.length;
+ var closingParenPos = line.indexOf(')', offset);
+ if (closingParenPos === -1) {
+ console.warn("Invalid include statement in '"
+ + FileInfo.toNativeSeparators(filePath) + "'");
+ continue;
+ }
+ var includedFilePath = line.slice(offset, closingParenPos);
+ if (!FileInfo.isAbsolutePath(includedFilePath))
+ includedFilePath = FileInfo.joinPaths(FileInfo.path(filePath), includedFilePath);
+ var includedContents = getFileContentsRecursively(includedFilePath);
+ var j = i;
+ for (var k = 0; k < includedContents.length; ++k)
+ lines.splice(++j, 0, includedContents[k]);
+ lines.splice(i--, 1);
+ }
+ file.close();
+ return lines;
+}
+
+function extractPaths(rhs, filePath) {
+ var paths = [];
+ var startIndex = 0;
+ for (;;) {
+ while (startIndex < rhs.length && rhs.charAt(startIndex) === ' ')
+ ++startIndex;
+ if (startIndex >= rhs.length)
+ break;
+ var endIndex;
+ if (rhs.charAt(startIndex) === '"') {
+ ++startIndex;
+ endIndex = rhs.indexOf('"', startIndex);
+ if (endIndex === -1) {
+ console.warn("Unmatched quote in file '"
+ + FileInfo.toNativeSeparators(filePath) + "'");
+ break;
+ }
+ } else {
+ endIndex = rhs.indexOf(' ', startIndex + 1);
+ if (endIndex === -1)
+ endIndex = rhs.length;
+ }
+ paths.push(FileInfo.cleanPath(rhs.slice(startIndex, endIndex)
+ .replace("$$PWD", FileInfo.path(filePath))));
+ startIndex = endIndex + 1;
+ }
+ return paths;
+}
+
+function removeDuplicatedDependencyLibs(modules) {
+ var revDeps = {};
+ var currentPath = [];
+ var getLibraries;
+ var getLibFilePath;
+
+ function setupReverseDependencies(modules) {
+ var moduleByName = {};
+ for (var i = 0; i < modules.length; ++i)
+ moduleByName[modules[i].qbsName] = modules[i];
+ for (i = 0; i < modules.length; ++i) {
+ var module = modules[i];
+ for (var j = 0; j < module.dependencies.length; ++j) {
+ var depmod = moduleByName[module.dependencies[j]];
+ if (!depmod)
+ continue;
+ if (!revDeps[depmod.qbsName])
+ revDeps[depmod.qbsName] = [];
+ revDeps[depmod.qbsName].push(module);
+ }
+ }
+ }
+
+ function roots(modules) {
+ var result = [];
+ for (i = 0; i < modules.length; ++i) {
+ var module = modules[i]
+ if (module.dependencies.length === 0)
+ result.push(module);
+ }
+ return result;
+ }
+
+ function traverse(module, libs) {
+ if (currentPath.contains(module))
+ return;
+ currentPath.push(module);
+ var moduleLibraryLists = getLibraries(module);
+ for (var i = 0; i < moduleLibraryLists.length; ++i) {
+ var modLibList = moduleLibraryLists[i];
+ for (j = modLibList.length - 1; j >= 0; --j) {
+ if (libs.contains(modLibList[j]))
+ modLibList.splice(j, 1);
+ }
+ }
+
+ var libFilePath = getLibFilePath(module);
+ if (libFilePath)
+ libs.push(libFilePath);
+ for (i = 0; i < moduleLibraryLists.length; ++i)
+ libs = libs.concat(moduleLibraryLists[i]);
+ libs.sort();
+
+ var deps = revDeps[module.qbsName];
+ for (i = 0; i < (deps || []).length; ++i)
+ traverse(deps[i], libs);
+
+ currentPath.pop();
+ }
+
+ setupReverseDependencies(modules);
+
+ // Traverse the debug variants of modules.
+ getLibraries = function(module) {
+ return [module.dynamicLibrariesDebug, module.staticLibrariesDebug];
+ };
+ getLibFilePath = function(module) { return module.libFilePathDebug; };
+ var rootModules = roots(modules);
+ for (var i = 0; i < rootModules.length; ++i)
+ traverse(rootModules[i], []);
+
+ // Traverse the release variants of modules.
+ getLibraries = function(module) {
+ return [module.dynamicLibrariesRelease, module.staticLibrariesRelease];
+ };
+ getLibFilePath = function(module) { return module.libFilePathRelease; };
+ for (i = 0; i < rootModules.length; ++i)
+ traverse(rootModules[i], []);
+}
+
+function allQt5Modules(qtProps, androidAbi) {
+ var nonExistingPrlFiles = [];
+ var modules = [];
+ var modulesDir = FileInfo.joinPaths(qtProps.mkspecBasePath, "modules");
+ var modulePriFiles = File.directoryEntries(modulesDir, File.Files);
+ for (var i = 0; i < modulePriFiles.length; ++i) {
+ var priFileName = modulePriFiles[i];
+ var priFilePath = FileInfo.joinPaths(modulesDir, priFileName);
+ var externalFileNamePrefix = "qt_ext_";
+ var moduleFileNamePrefix = "qt_lib_";
+ var pluginFileNamePrefix = "qt_plugin_";
+ var moduleFileNameSuffix = ".pri";
+ var fileHasExternalPrefix = priFileName.startsWith(externalFileNamePrefix);
+ var fileHasModulePrefix = priFileName.startsWith(moduleFileNamePrefix);
+ var fileHasPluginPrefix = priFileName.startsWith(pluginFileNamePrefix);
+ if (!fileHasPluginPrefix && !fileHasModulePrefix && !fileHasExternalPrefix
+ || !priFileName.endsWith(moduleFileNameSuffix)) {
+ continue;
+ }
+ var moduleInfo = makeQtModuleInfo();
+ moduleInfo.isPlugin = fileHasPluginPrefix;
+ moduleInfo.isExternal = !moduleInfo.isPlugin && !fileHasModulePrefix;
+ var fileNamePrefix = moduleInfo.isPlugin ? pluginFileNamePrefix : moduleInfo.isExternal
+ ? externalFileNamePrefix : moduleFileNamePrefix;
+ moduleInfo.qbsName = priFileName.slice(fileNamePrefix.length, -moduleFileNameSuffix.length);
+ if (moduleInfo.isPlugin) {
+ moduleInfo.name = moduleInfo.qbsName;
+ moduleInfo.isStaticLibrary = true;
+ }
+ var moduleKeyPrefix = (moduleInfo.isPlugin ? "QT_PLUGIN" : "QT")
+ + '.' + moduleInfo.qbsName + '.';
+ moduleInfo.qbsName = moduleInfo.qbsName.replace("_private", "-private");
+ var hasV2 = false;
+ var hasModuleEntry = false;
+ var lines = getFileContentsRecursively(priFilePath);
+ if (moduleInfo.isExternal) {
+ moduleInfo.name = "qt" + moduleInfo.qbsName;
+ moduleInfo.isStaticLibrary = true;
+ for (var k = 0; k < lines.length; ++k) {
+ var extLine = lines[k].trim();
+ var extFirstEqualsOffset = extLine.indexOf('=');
+ if (extFirstEqualsOffset === -1)
+ continue;
+ var extKey = extLine.slice(0, extFirstEqualsOffset).trim();
+ var extValue = extLine.slice(extFirstEqualsOffset + 1).trim();
+ if (!extKey.startsWith("QMAKE_") || !extValue)
+ continue;
+
+ var elements = extKey.split('_');
+ if (elements.length >= 3) {
+ if (elements[1] === "LIBS") {
+ extValue = extValue.replace("/home/qt/work/qt/qtbase/lib",
+ qtProps.libraryPath);
+ extValue = extValue.replace("$$[QT_INSTALL_LIBS]", qtProps.libraryPath);
+ extValue = extValue.replace("$$[QT_INSTALL_LIBS/get]", qtProps.libraryPath);
+ if (elements.length === 4 ) {
+ if (elements[3] === androidAbi) {
+ moduleInfo.staticLibrariesRelease.push(extValue);
+ moduleInfo.staticLibrariesDebug.push(extValue);
+ }
+ } else if (elements.length === 5 ) {
+ // That's for "x86_64"
+ var abi = elements[3] + '_' + elements[4];
+ if (abi === androidAbi) {
+ moduleInfo.staticLibrariesRelease.push(extValue);
+ moduleInfo.staticLibrariesDebug.push(extValue);
+ }
+ } else {
+ moduleInfo.staticLibrariesRelease.push(extValue);
+ moduleInfo.staticLibrariesDebug.push(extValue);
+ }
+ } else if (elements[1] === "INCDIR") {
+ moduleInfo.includePaths.push(extValue.replace("$$[QT_INSTALL_HEADERS]",
+ qtProps.includePath));
+ }
+ }
+ }
+ moduleInfo.compilerDefines.push("QT_" + moduleInfo.qbsName.toUpperCase() + "_LIB");
+ moduleInfo.mustExist = false;
+ } else {
+ for (var j = 0; j < lines.length; ++j) {
+ var line = lines[j].trim();
+ var firstEqualsOffset = line.indexOf('=');
+ if (firstEqualsOffset === -1)
+ continue;
+ var key = line.slice(0, firstEqualsOffset).trim();
+ var value = line.slice(firstEqualsOffset + 1).trim();
+ if (!key.startsWith(moduleKeyPrefix) || !value)
+ continue;
+ if (key.endsWith(".name")) {
+ moduleInfo.name = value;
+ } else if (key.endsWith(".module")) {
+ hasModuleEntry = true;
+ } else if (key.endsWith(".depends")) {
+ moduleInfo.dependencies = splitNonEmpty(value, ' ');
+ for (var k = 0; k < moduleInfo.dependencies.length; ++k) {
+ moduleInfo.dependencies[k]
+ = moduleInfo.dependencies[k].replace("_private", "-private");
+ }
+ } else if (key.endsWith(".module_config")) {
+ var elems = splitNonEmpty(value, ' ');
+ for (k = 0; k < elems.length; ++k) {
+ var elem = elems[k];
+ if (elem === "no_link")
+ moduleInfo.hasLibrary = false;
+ else if (elem === "staticlib")
+ moduleInfo.isStaticLibrary = true;
+ else if (elem === "internal_module")
+ moduleInfo.isPrivate = true;
+ else if (elem === "v2")
+ hasV2 = true;
+ }
+ } else if (key.endsWith(".includes")) {
+ moduleInfo.includePaths = extractPaths(value, priFilePath);
+ for (k = 0; k < moduleInfo.includePaths.length; ++k) {
+ moduleInfo.includePaths[k] = moduleInfo.includePaths[k]
+ .replace("$$QT_MODULE_INCLUDE_BASE", qtProps.includePath)
+ .replace("$$QT_MODULE_HOST_LIB_BASE", qtProps.hostLibraryPath)
+ .replace("$$QT_MODULE_LIB_BASE", qtProps.libraryPath);
+ }
+ } else if (key.endsWith(".libs")) {
+ var libDirs = extractPaths(value, priFilePath);
+ if (libDirs.length === 1) {
+ moduleInfo.libDir = libDirs[0]
+ .replace("$$QT_MODULE_HOST_LIB_BASE", qtProps.hostLibraryPath)
+ .replace("$$QT_MODULE_LIB_BASE", qtProps.libraryPath);
+ } else {
+ moduleInfo.libDir = qtProps.libraryPath;
+ }
+ } else if (key.endsWith(".DEFINES")) {
+ moduleInfo.compilerDefines = splitNonEmpty(value, ' ');
+ } else if (key.endsWith(".VERSION")) {
+ moduleInfo.version = value;
+ } else if (key.endsWith(".plugin_types")) {
+ moduleInfo.supportedPluginTypes = splitNonEmpty(value, ' ');
+ } else if (key.endsWith(".TYPE")) {
+ moduleInfo.pluginData.type = value;
+ } else if (key.endsWith(".EXTENDS")) {
+ moduleInfo.pluginData["extends"] = splitNonEmpty(value, ' ');
+ for (k = 0; k < moduleInfo.pluginData["extends"].length; ++k) {
+ if (moduleInfo.pluginData["extends"][k] === "-") {
+ moduleInfo.pluginData["extends"].splice(k, 1);
+ moduleInfo.pluginData.autoLoad = false;
+ break;
+ }
+ }
+ } else if (key.endsWith(".CLASS_NAME")) {
+ moduleInfo.pluginData.className = value;
+ }
+ }
+ }
+ if (hasV2 && !hasModuleEntry && !moduleInfo.isStaticLibrary)
+ moduleInfo.hasLibrary = false;
+
+ // Fix include paths for Apple frameworks.
+ // The qt_lib_XXX.pri files contain wrong values for versions < 5.6.
+ if (!hasV2 && ProviderUtils.qtIsFramework(moduleInfo, qtProps)) {
+ moduleInfo.includePaths = [];
+ var baseIncDir = frameworkHeadersPath(moduleInfo, qtProps);
+ if (moduleInfo.isPrivate) {
+ baseIncDir = FileInfo.joinPaths(baseIncDir, moduleInfo.version);
+ moduleInfo.includePaths.push(baseIncDir,
+ FileInfo.joinPaths(baseIncDir, moduleInfo.name));
+ } else {
+ moduleInfo.includePaths.push(baseIncDir);
+ }
+ }
+
+ setupLibraries(moduleInfo, qtProps, nonExistingPrlFiles, androidAbi);
+
+ modules.push(moduleInfo);
+ if (moduleInfo.qbsName === "testlib")
+ addTestModule(modules);
+ if (moduleInfo.qbsName === "designercomponents-private")
+ addDesignerComponentsModule(modules);
+ }
+
+ replaceQtLibNamesWithFilePath(modules, qtProps);
+ removeDuplicatedDependencyLibs(modules);
+
+ return modules;
+}
+
+function getQtInfo(qmakeFilePath) {
+ if (!File.exists(qmakeFilePath)) {
+ throw "The specified qmake file path '"
+ + FileInfo.toNativeSeparators(qmakeFilePath) + "' does not exist.";
+ }
+ var qtProps = getQtProperties(qmakeFilePath);
+ var androidAbis = [];
+ if (qtProps.androidAbis !== undefined) {
+ // Multiple androidAbis detected: Qt >= 5.14
+ androidAbis = qtProps.androidAbis;
+ } else {
+ // Single abi detected: Qt < 5.14
+ androidAbis.push('');
+ }
+ if (androidAbis.length > 1)
+ console.info("Qt with multiple abi detected: '" + androidAbis + "'");
+
+ var result = {};
+ result.qtProps = qtProps;
+ result.abiInfos = [];
+ result.qmakeFilePath = qmakeFilePath;
+ for (a = 0; a < androidAbis.length; ++a) {
+ var abiInfo = {};
+ if (androidAbis.length > 1)
+ console.info("Found abi '" + androidAbis[a] + "'...");
+ abiInfo.androidAbi = androidAbis[a];
+ var allModules = qtProps.qtMajorVersion < 5
+ ? allQt4Modules(qtProps) : allQt5Modules(qtProps, androidAbis[a]);;
+ abiInfo.modules = {};
+ for (var i = 0; i < allModules.length; ++i) {
+ var module = allModules[i];
+ abiInfo.modules[module.qbsName] = module;
+ }
+ abiInfo.pluginsByType = {};
+ abiInfo.nonEssentialPlugins = [];
+ for (var moduleName in abiInfo.modules) {
+ var m = abiInfo.modules[moduleName];
+ if (m.isPlugin) {
+ if (!abiInfo.pluginsByType[m.pluginData.type])
+ abiInfo.pluginsByType[m.pluginData.type] = [];
+ abiInfo.pluginsByType[m.pluginData.type].push(m.name);
+ if (!m.pluginData.autoLoad)
+ abiInfo.nonEssentialPlugins.push(m.name);
+ }
+ }
+
+ result.abiInfos.push(abiInfo);
+ }
+ return result;
+}
+
+function configure(qmakeFilePaths) {
+ var result = [];
+ qmakeFilePaths = getQmakeFilePaths(qmakeFilePaths);
+ if (!qmakeFilePaths || qmakeFilePaths.length === 0)
+ return result;
+ for (var i = 0; i < qmakeFilePaths.length; ++i) {
+ var qtInfo = {};
+ try {
+ console.info("Getting info about Qt at '"
+ + FileInfo.toNativeSeparators(qmakeFilePaths[i]) + "'...");
+ qtInfo = getQtInfo(qmakeFilePaths[i]);
+ result.push(qtInfo);
+ } catch (e) {
+ console.warn("Error getting info about Qt for '"
+ + FileInfo.toNativeSeparators(qmakeFilePaths[i]) + "': " + e);
+ throw e;
+ }
+ }
+ return result;
+}
diff --git a/share/qbs/imports/qbs/ProviderUtils/provider-utils.js b/share/qbs/imports/qbs/ProviderUtils/provider-utils.js
new file mode 100644
index 000000000..06e703a7a
--- /dev/null
+++ b/share/qbs/imports/qbs/ProviderUtils/provider-utils.js
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Copyright (C) 2023 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.
+**
+****************************************************************************/
+
+var Utilities = require("qbs.Utilities");
+
+function pkgConfigToModuleName(packageName) {
+ return packageName.replace(/\./g, '-');
+}
+
+function msvcPrefix() { return "win32-msvc"; }
+
+function isMsvcQt(qtProps) { return qtProps.mkspecName.startsWith(msvcPrefix()); }
+
+function isMinGwQt(qtProps) {
+ return qtProps.mkspecName.startsWith("win32-g++") || qtProps.mkspecName.startsWith("mingw");
+}
+
+function isDesktopWindowsQt(qtProps) {
+ return qtProps.mkspecName.startsWith("win32-") || isMinGwQt(qtProps);
+}
+
+function qtNeedsDSuffix(qtProps) {
+ return !isMinGwQt(qtProps)
+ || Utilities.versionCompare(qtProps.qtVersion, "5.14.0") < 0
+ || qtProps.configItems.contains("debug_and_release");
+}
+
+function qtIsFramework(modInfo, qtProps) {
+ if (!qtProps.frameworkBuild || modInfo.isStaticLibrary)
+ return false;
+ var modulesNeverBuiltAsFrameworks = [
+ "bootstrap", "openglextensions", "platformsupport", "qmldevtools", "harfbuzzng"
+ ];
+
+ if (qtProps.qtMajorVersion <= 5) {
+ modulesNeverBuiltAsFrameworks.push("uitools"); // is framework since qt6
+ }
+
+ return !modulesNeverBuiltAsFrameworks.contains(modInfo.qbsName);
+}
+
+function qtLibBaseName(modInfo, libName, debugBuild, qtProps) {
+ var name = libName;
+ if (qtProps.mkspecName.startsWith("win")) {
+ if (debugBuild && qtNeedsDSuffix(qtProps))
+ name += 'd';
+ if (!modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5)
+ name += qtProps.qtMajorVersion;
+ }
+ if (qtProps.mkspecName.contains("macx")
+ || qtProps.mkspecName.contains("ios")
+ || qtProps.mkspecName.contains("darwin")) {
+ if (!qtIsFramework(modInfo, qtProps)
+ && qtProps.buildVariant.contains("debug")
+ && (!qtProps.buildVariant.contains("release") || debugBuild)) {
+ name += "_debug";
+ }
+ }
+ return name;
+}
+
+function qtModuleNameWithoutPrefix(modInfo) {
+ if (modInfo.name === "Phonon")
+ return "phonon";
+ if (!modInfo.modulePrefix && modInfo.name.startsWith("Qt"))
+ return modInfo.name.slice(2); // Strip off "Qt".
+ if (modInfo.name.startsWith(modInfo.modulePrefix))
+ return modInfo.name.slice(modInfo.modulePrefix.length);
+ return modInfo.name;
+}
+
+function qtLibraryBaseName(modInfo, qtProps, debugBuild) {
+ if (modInfo.isPlugin)
+ return qtLibBaseName(modInfo, modInfo.name, debugBuild, qtProps);
+
+ // Some modules use a different naming scheme, so it doesn't get boring.
+ var libNameBroken = modInfo.name === "Enginio"
+ || modInfo.name === "DataVisualization"
+ || modInfo.name === "Phonon";
+
+ var libName = "";
+ if (!modInfo.isExternal) {
+ libName += !modInfo.modulePrefix && !libNameBroken ? "Qt" : modInfo.modulePrefix;
+ if (qtProps.qtMajorVersion >= 5 && !qtIsFramework(modInfo, qtProps) && !libNameBroken)
+ libName += qtProps.qtMajorVersion;
+ }
+ libName += qtModuleNameWithoutPrefix(modInfo);
+ if (!modInfo.isExternal)
+ libName += qtProps.qtLibInfix;
+ return qtLibBaseName(modInfo, libName, debugBuild, qtProps);
+}
+
+function qtLibNameForLinker(modInfo, qtProps, debugBuild) {
+ if (!modInfo.hasLibrary)
+ return undefined;
+ var libName = qtLibraryBaseName(modInfo, qtProps, debugBuild);
+ if (qtProps.mkspecName.contains("msvc"))
+ libName += ".lib";
+ return libName;
+}
diff --git a/share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs b/share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs
index 6491bf14a..462b96b7c 100644
--- a/share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs
+++ b/share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs
@@ -34,7 +34,8 @@ import qbs.ModUtils
AppleDiskImage {
property string sourceBase: "/Applications"
- readonly property string absoluteSourceBase: FileInfo.joinPaths(qbs.installRoot, sourceBase)
+ readonly property string absoluteSourceBase:
+ FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix, sourceBase)
property stringList symlinks: ["/Applications:Applications"]
readonly property string stageDirectory: FileInfo.joinPaths(destinationDirectory, "Volumes", dmg.volumeName)
diff --git a/share/qbs/imports/qbs/base/ApplicationExtension.qbs b/share/qbs/imports/qbs/base/ApplicationExtension.qbs
index eae5145ce..3d25d5f73 100644
--- a/share/qbs/imports/qbs/base/ApplicationExtension.qbs
+++ b/share/qbs/imports/qbs/base/ApplicationExtension.qbs
@@ -34,8 +34,12 @@ XPCService {
type: base.concat(["applicationextension"])
property bool _useLegacyExtensionLibraries:
- qbs.targetOS.contains("macos") && parseInt(xcode.sdkVersion.split(".")[1], 10) < 11 ||
- qbs.targetOS.contains("ios") && parseInt(xcode.sdkVersion.split(".")[0], 10) < 9
+ qbs.targetOS.contains("macos")
+ && xcode.present
+ && parseInt(xcode.sdkVersion.split(".")[1], 10) < 11
+ || qbs.targetOS.contains("ios")
+ && xcode.present
+ && parseInt(xcode.sdkVersion.split(".")[0], 10) < 9
cpp.entryPoint: "_NSExtensionMain"
cpp.frameworkPaths: base.concat(_useLegacyExtensionLibraries
diff --git a/share/qbs/module-providers/Qt/templates/QtModule.qbs b/share/qbs/imports/qbs/base/QtModule.qbs
index 35421436f..35421436f 100644
--- a/share/qbs/module-providers/Qt/templates/QtModule.qbs
+++ b/share/qbs/imports/qbs/base/QtModule.qbs
diff --git a/share/qbs/module-providers/Qt/templates/QtPlugin.qbs b/share/qbs/imports/qbs/base/QtPlugin.qbs
index 883e34465..883e34465 100644
--- a/share/qbs/module-providers/Qt/templates/QtPlugin.qbs
+++ b/share/qbs/imports/qbs/base/QtPlugin.qbs
diff --git a/share/qbs/module-providers/Qt/provider.qbs b/share/qbs/module-providers/Qt/provider.qbs
index be026b1e0..0d036c04d 100644
--- a/share/qbs/module-providers/Qt/provider.qbs
+++ b/share/qbs/module-providers/Qt/provider.qbs
@@ -1,6 +1,14 @@
import "setup-qt.js" as SetupQt
+import qbs.Probes
ModuleProvider {
+ Probes.QmakeProbe {
+ id: probe
+ qmakePaths: qmakeFilePaths
+ }
property stringList qmakeFilePaths
- relativeSearchPaths: SetupQt.doSetup(qmakeFilePaths, outputBaseDir, path)
+ readonly property varList _qtInfos: probe.qtInfos
+ condition: moduleName.startsWith("Qt.")
+ isEager: false
+ relativeSearchPaths: SetupQt.doSetup(moduleName, _qtInfos, outputBaseDir, path)
}
diff --git a/share/qbs/module-providers/Qt/setup-qt.js b/share/qbs/module-providers/Qt/setup-qt.js
index b33b30e3a..9a314822b 100644
--- a/share/qbs/module-providers/Qt/setup-qt.js
+++ b/share/qbs/module-providers/Qt/setup-qt.js
@@ -37,1243 +37,13 @@
**
****************************************************************************/
-var Environment = require("qbs.Environment");
var File = require("qbs.File");
var FileInfo = require("qbs.FileInfo");
-var Host = require("qbs.Host");
var ModUtils = require("qbs.ModUtils");
-var Process = require("qbs.Process");
+var ProviderUtils = require("qbs.ProviderUtils");
var TextFile = require("qbs.TextFile");
var Utilities = require("qbs.Utilities");
-function splitNonEmpty(s, c) { return s.split(c).filter(function(e) { return e; }); }
-function toNative(p) { return FileInfo.toNativeSeparators(p); }
-
-function getQmakeFilePaths(qmakeFilePaths) {
- if (qmakeFilePaths && qmakeFilePaths.length > 0)
- return qmakeFilePaths;
- console.info("Detecting Qt installations...");
- var filePaths = [];
- var pathValue = Environment.getEnv("PATH");
- if (pathValue) {
- var dirs = splitNonEmpty(pathValue, FileInfo.pathListSeparator());
- for (var i = 0; i < dirs.length; ++i) {
- var candidate = FileInfo.joinPaths(dirs[i], "qmake" + FileInfo.executableSuffix());
- var canonicalCandidate = FileInfo.canonicalPath(candidate);
- if (!canonicalCandidate || !File.exists(canonicalCandidate))
- continue;
- if (FileInfo.completeBaseName(canonicalCandidate) !== "qtchooser")
- candidate = canonicalCandidate;
- if (!filePaths.contains(candidate)) {
- console.info("Found Qt at '" + toNative(candidate) + "'.");
- filePaths.push(candidate);
- }
- }
- }
- if (filePaths.length === 0) {
- console.warn("Could not find any qmake executables in PATH. Either make sure a qmake "
- + "executable is present in PATH or set the moduleProviders.Qt.qmakeFilePaths property "
- + "to point a qmake executable.");
- }
- return filePaths;
-}
-
-function queryQmake(qmakeFilePath) {
- var qmakeProcess = new Process;
- qmakeProcess.exec(qmakeFilePath, ["-query"]);
- if (qmakeProcess.exitCode() !== 0) {
- throw "The qmake executable '" + toNative(qmakeFilePath) + "' failed with exit code "
- + qmakeProcess.exitCode() + ".";
- }
- var queryResult = {};
- while (!qmakeProcess.atEnd()) {
- var line = qmakeProcess.readLine();
- var index = (line || "").indexOf(":");
- if (index !== -1)
- queryResult[line.slice(0, index)] = line.slice(index + 1).trim();
- }
- return queryResult;
-}
-
-function pathQueryValue(queryResult, key) {
- var p = queryResult[key];
- if (p)
- return FileInfo.fromNativeSeparators(p);
-}
-
-function readFileContent(filePath) {
- var f = new TextFile(filePath, TextFile.ReadOnly);
- var content = f.readAll();
- f.close();
- return content;
-}
-
-// TODO: Don't do the split every time...
-function configVariable(configContent, key) {
- var configContentLines = configContent.split('\n');
- var regexp = new RegExp("^\\s*" + key + "\\s*\\+{0,1}=(.*)");
- for (var i = 0; i < configContentLines.length; ++i) {
- var line = configContentLines[i];
- var match = regexp.exec(line);
- if (match)
- return match[1].trim();
- }
-}
-
-function configVariableItems(configContent, key) {
- return splitNonEmpty(configVariable(configContent, key), ' ');
-}
-
-function msvcPrefix() { return "win32-msvc"; }
-
-function isMsvcQt(qtProps) { return qtProps.mkspecName.startsWith(msvcPrefix()); }
-
-function msvcCompilerVersionForYear(year) {
- var mapping = {
- "2005": "14", "2008": "15", "2010": "16", "2012": "17", "2013": "18", "2015": "19",
- "2017": "19.1", "2019": "19.2"
- };
- return mapping[year];
-}
-
-function msvcCompilerVersionFromMkspecName(mkspecName) {
- return msvcCompilerVersionForYear(mkspecName.slice(msvcPrefix().length));
-}
-
-function addQtBuildVariant(qtProps, buildVariantName) {
- if (qtProps.qtConfigItems.contains(buildVariantName))
- qtProps.buildVariant.push(buildVariantName);
-}
-
-function checkForStaticBuild(qtProps) {
- if (qtProps.qtMajorVersion >= 5)
- return qtProps.qtConfigItems.contains("static");
- if (qtProps.frameworkBuild)
- return false; // there are no Qt4 static frameworks
- var isWin = qtProps.mkspecName.startsWith("win");
- var libDir = isWin ? qtProps.binaryPath : qtProps.libraryPath;
- var coreLibFiles = File.directoryEntries(libDir, File.Files)
- .filter(function(fp) { return fp.contains("Core"); });
- if (coreLibFiles.length === 0)
- throw "Could not determine whether Qt is a static build.";
- for (var i = 0; i < coreLibFiles.length; ++i) {
- if (Utilities.isSharedLibrary(coreLibFiles[i]))
- return false;
- }
- return true;
-}
-
-function isForMinGw(qtProps) {
- return qtProps.mkspecName.startsWith("win32-g++") || qtProps.mkspecName.startsWith("mingw");
-}
-
-function targetsDesktopWindows(qtProps) {
- return qtProps.mkspecName.startsWith("win32-") || isForMinGw(qtProps);
-}
-
-function guessMinimumWindowsVersion(qtProps) {
- if (qtProps.mkspecName.startsWith("winrt-"))
- return "10.0";
- if (!targetsDesktopWindows(qtProps))
- return "";
- if (qtProps.qtMajorVersion >= 6)
- return "10.0";
- if (qtProps.architecture === "x86_64" || qtProps.architecture === "ia64")
- return "5.2"
- var match = qtProps.mkspecName.match(/^win32-msvc(\d+)$/);
- if (match) {
- var msvcVersion = match[1];
- if (msvcVersion < 2012)
- return "5.0";
- return "5.1";
- }
- return qtProps.qtMajorVersion < 5 ? "5.0" : "5.1";
-}
-
-function needsDSuffix(qtProps) {
- return !isForMinGw(qtProps) || Utilities.versionCompare(qtProps.qtVersion, "5.14.0") < 0
- || qtProps.configItems.contains("debug_and_release");
-}
-
-function fillEntryPointLibs(qtProps, debug) {
- result = [];
- var isMinGW = isForMinGw(qtProps);
-
- // Some Linux distributions rename the qtmain library.
- var qtMainCandidates = ["qtmain"];
- if (isMinGW && qtProps.qtMajorVersion === 5)
- qtMainCandidates.push("qt5main");
- if (qtProps.qtMajorVersion === 6)
- qtMainCandidates.push("Qt6EntryPoint");
-
- for (var i = 0; i < qtMainCandidates.length; ++i) {
- var baseNameCandidate = qtMainCandidates[i];
- var qtmain = qtProps.libraryPath + '/';
- if (isMinGW)
- qtmain += "lib";
- qtmain += baseNameCandidate + qtProps.qtLibInfix;
- if (debug && needsDSuffix(qtProps))
- qtmain += 'd';
- if (isMinGW) {
- qtmain += ".a";
- } else {
- qtmain += ".lib";
- if (Utilities.versionCompare(qtProps.qtVersion, "5.4.0") >= 0)
- result.push("Shell32.lib");
- }
- if (File.exists(qtmain)) {
- result.push(qtmain);
- break;
- }
- }
- if (result.length === 0) {
- console.warn("Could not find the qtmain library at '" + toNative(qtProps.libraryPath)
- + "'. You will not be able to link Qt applications.");
- }
- return result;
-}
-
-function abiToArchitecture(abi) {
- switch (abi) {
- case "armeabi-v7a":
- return "armv7a";
- case "arm64-v8a":
- return "arm64";
- case "x86":
- case "x86_64":
- default:
- return abi;
- }
-}
-
-function getQtProperties(qmakeFilePath) {
- var queryResult = queryQmake(qmakeFilePath);
- var qtProps = {};
- qtProps.installPrefixPath = pathQueryValue(queryResult, "QT_INSTALL_PREFIX");
- qtProps.documentationPath = pathQueryValue(queryResult, "QT_INSTALL_DOCS");
- qtProps.includePath = pathQueryValue(queryResult, "QT_INSTALL_HEADERS");
- qtProps.libraryPath = pathQueryValue(queryResult, "QT_INSTALL_LIBS");
- qtProps.hostLibraryPath = pathQueryValue(queryResult, "QT_HOST_LIBS");
- qtProps.binaryPath = pathQueryValue(queryResult, "QT_HOST_BINS")
- || pathQueryValue(queryResult, "QT_INSTALL_BINS");
- qtProps.installPath = pathQueryValue(queryResult, "QT_INSTALL_BINS");
- qtProps.documentationPath = pathQueryValue(queryResult, "QT_INSTALL_DOCS");
- qtProps.pluginPath = pathQueryValue(queryResult, "QT_INSTALL_PLUGINS");
- qtProps.qmlPath = pathQueryValue(queryResult, "QT_INSTALL_QML");
- qtProps.qmlImportPath = pathQueryValue(queryResult, "QT_INSTALL_IMPORTS");
- qtProps.qtVersion = queryResult.QT_VERSION;
-
- var mkspecsBaseSrcPath;
- if (Utilities.versionCompare(qtProps.qtVersion, "5") >= 0) {
- qtProps.mkspecBasePath = FileInfo.joinPaths(pathQueryValue(queryResult, "QT_HOST_DATA"),
- "mkspecs");
- mkspecsBaseSrcPath = FileInfo.joinPaths(pathQueryValue(queryResult, "QT_HOST_DATA/src"),
- "mkspecs");
- } else {
- qtProps.mkspecBasePath = FileInfo.joinPaths
- (pathQueryValue(queryResult, "QT_INSTALL_DATA"), "mkspecs");
- }
-
- if (Utilities.versionCompare(qtProps.qtVersion, "6") >= 0) {
- qtProps.libExecPath = pathQueryValue(queryResult, "QT_HOST_LIBEXECS")
- || pathQueryValue(queryResult, "QT_INSTALL_LIBEXECS");
- }
-
- // QML tools were only moved in Qt 6.2.
- qtProps.qmlLibExecPath = Utilities.versionCompare(qtProps.qtVersion, "6.2") >= 0
- ? qtProps.libExecPath : qtProps.binaryPath;
-
- // qhelpgenerator was only moved in Qt 6.3.
- qtProps.helpGeneratorLibExecPath = Utilities.versionCompare(qtProps.qtVersion, "6.3") >= 0
- ? qtProps.libExecPath : qtProps.binaryPath;
-
- if (!File.exists(qtProps.mkspecBasePath))
- throw "Cannot extract the mkspecs directory.";
-
- var qconfigContent = readFileContent(FileInfo.joinPaths(qtProps.mkspecBasePath,
- "qconfig.pri"));
- qtProps.qtMajorVersion = parseInt(configVariable(qconfigContent, "QT_MAJOR_VERSION"));
- qtProps.qtMinorVersion = parseInt(configVariable(qconfigContent, "QT_MINOR_VERSION"));
- qtProps.qtPatchVersion = parseInt(configVariable(qconfigContent, "QT_PATCH_VERSION"));
- qtProps.qtNameSpace = configVariable(qconfigContent, "QT_NAMESPACE");
- qtProps.qtLibInfix = configVariable(qconfigContent, "QT_LIBINFIX") || "";
- qtProps.architecture = configVariable(qconfigContent, "QT_TARGET_ARCH")
- || configVariable(qconfigContent, "QT_ARCH") || "x86";
- qtProps.configItems = configVariableItems(qconfigContent, "CONFIG");
- qtProps.qtConfigItems = configVariableItems(qconfigContent, "QT_CONFIG");
-
- // retrieve the mkspec
- if (qtProps.qtMajorVersion >= 5) {
- qtProps.mkspecName = queryResult.QMAKE_XSPEC;
- qtProps.mkspecPath = FileInfo.joinPaths(qtProps.mkspecBasePath, qtProps.mkspecName);
- if (mkspecsBaseSrcPath && !File.exists(qtProps.mkspecPath))
- qtProps.mkspecPath = FileInfo.joinPaths(mkspecsBaseSrcPath, qtProps.mkspecName);
- } else {
- if (Host.os().contains("windows")) {
- var baseDirPath = FileInfo.joinPaths(qtProps.mkspecBasePath, "default");
- var fileContent = readFileContent(FileInfo.joinPaths(baseDirPath, "qmake.conf"));
- qtProps.mkspecPath = configVariable(fileContent, "QMAKESPEC_ORIGINAL");
- if (!File.exists(qtProps.mkspecPath)) {
- // Work around QTBUG-28792.
- // The value of QMAKESPEC_ORIGINAL is wrong for MinGW packages. Y u h8 me?
- var match = fileContent.exec(/\binclude\(([^)]+)\/qmake\.conf\)/m);
- if (match) {
- qtProps.mkspecPath = FileInfo.cleanPath(FileInfo.joinPaths(
- baseDirPath, match[1]));
- }
- }
- } else {
- qtProps.mkspecPath = FileInfo.canonicalPath(
- FileInfo.joinPaths(qtProps.mkspecBasePath, "default"));
- }
-
- // E.g. in qmake.conf for Qt 4.8/mingw we find this gem:
- // QMAKESPEC_ORIGINAL=C:\\Qt\\Qt\\4.8\\mingw482\\mkspecs\\win32-g++
- qtProps.mkspecPath = FileInfo.cleanPath(qtProps.mkspecPath);
-
- qtProps.mkspecName = qtProps.mkspecPath;
- var idx = qtProps.mkspecName.lastIndexOf('/');
- if (idx !== -1)
- qtProps.mkspecName = qtProps.mkspecName.slice(idx + 1);
- }
- if (!File.exists(qtProps.mkspecPath))
- throw "mkspec '" + toNative(qtProps.mkspecPath) + "' does not exist";
-
- // Starting with qt 5.14, android sdk provides multi-abi
- if (Utilities.versionCompare(qtProps.qtVersion, "5.14.0") >= 0
- && qtProps.mkspecPath.contains("android")) {
- var qdeviceContent = readFileContent(FileInfo.joinPaths(qtProps.mkspecBasePath,
- "qdevice.pri"));
- qtProps.androidAbis = configVariable(qdeviceContent, "DEFAULT_ANDROID_ABIS").split(' ');
- }
-
- // determine MSVC version
- if (isMsvcQt(qtProps)) {
- var msvcMajor = configVariable(qconfigContent, "QT_MSVC_MAJOR_VERSION");
- var msvcMinor = configVariable(qconfigContent, "QT_MSVC_MINOR_VERSION");
- var msvcPatch = configVariable(qconfigContent, "QT_MSVC_PATCH_VERSION");
- if (msvcMajor && msvcMinor && msvcPatch)
- qtProps.msvcVersion = msvcMajor + "." + msvcMinor + "." + msvcPatch;
- else
- qtProps.msvcVersion = msvcCompilerVersionFromMkspecName(qtProps.mkspecName);
- }
-
- // determine whether we have a framework build
- qtProps.frameworkBuild = qtProps.mkspecPath.contains("macx")
- && qtProps.configItems.contains("qt_framework");
-
- // determine whether Qt is built with debug, release or both
- qtProps.buildVariant = [];
- addQtBuildVariant(qtProps, "debug");
- addQtBuildVariant(qtProps, "release");
-
- qtProps.staticBuild = checkForStaticBuild(qtProps);
-
- // determine whether user apps require C++11
- if (qtProps.qtConfigItems.contains("c++11") && qtProps.staticBuild)
- qtProps.configItems.push("c++11");
-
- // Set the minimum operating system versions appropriate for this Qt version
- qtProps.windowsVersion = guessMinimumWindowsVersion(qtProps);
- if (qtProps.windowsVersion) { // Is target OS Windows?
- if (qtProps.buildVariant.contains("debug"))
- qtProps.entryPointLibsDebug = fillEntryPointLibs(qtProps, true);
- if (qtProps.buildVariant.contains("release"))
- qtProps.entryPointLibsRelease = fillEntryPointLibs(qtProps, false);
- } else if (qtProps.mkspecPath.contains("macx")) {
- if (qtProps.qtMajorVersion >= 5) {
- var lines = getFileContentsRecursively(FileInfo.joinPaths(qtProps.mkspecPath,
- "qmake.conf"));
- for (var i = 0; i < lines.length; ++i) {
- var line = lines[i].trim();
- match = line.match
- (/^QMAKE_(MACOSX|IOS|TVOS|WATCHOS)_DEPLOYMENT_TARGET\s*=\s*(.*)\s*$/);
- if (match) {
- var platform = match[1];
- var version = match[2];
- if (platform === "MACOSX")
- qtProps.macosVersion = version;
- else if (platform === "IOS")
- qtProps.iosVersion = version;
- else if (platform === "TVOS")
- qtProps.tvosVersion = version;
- else if (platform === "WATCHOS")
- qtProps.watchosVersion = version;
- }
- }
- var isMac = qtProps.mkspecName !== "macx-ios-clang"
- && qtProps.mkspecName !== "macx-tvos-clang"
- && qtProps.mkspecName !== "macx-watchos-clang";
- if (isMac) {
- // Qt 5.0.x placed the minimum version in a different file
- if (!qtProps.macosVersion)
- qtProps.macosVersion = "10.6";
-
- // If we're using C++11 with libc++, make sure the deployment target is >= 10.7
- if (Utilities.versionCompare(qtProps.macosVersion, "10, 7") < 0
- && qtProps.qtConfigItems.contains("c++11")) {
- qtProps.macosVersion = "10.7";
- }
- }
- } else if (qtProps.qtMajorVersion === 4 && qtProps.qtMinorVersion >= 6) {
- var qconfigDir = qtProps.frameworkBuild
- ? FileInfo.joinPaths(qtProps.libraryPath, "QtCore.framework", "Headers")
- : FileInfo.joinPaths(qtProps.includePath, "Qt");
- try {
- var qconfig = new TextFile(FileInfo.joinPaths(qconfigDir, "qconfig.h"),
- TextFile.ReadOnly);
- var qtCocoaBuild = false;
- var ok = true;
- do {
- line = qconfig.readLine();
- if (line.match(/\s*#define\s+QT_MAC_USE_COCOA\s+1\s*/)) {
- qtCocoaBuild = true;
- break;
- }
- } while (!qconfig.atEof());
- qtProps.macosVersion = qtCocoaBuild ? "10.5" : "10.4";
- }
- catch (e) {}
- finally {
- if (qconfig)
- qconfig.close();
- }
- if (!qtProps.macosVersion) {
- throw "Could not determine whether Qt is using Cocoa or Carbon from '"
- + toNative(qconfig.filePath()) + "'.";
- }
- }
- } else if (qtProps.mkspecPath.contains("android")) {
- if (qtProps.qtMajorVersion >= 5)
- qtProps.androidVersion = "2.3";
- else if (qtProps.qtMajorVersion === 4 && qtProps.qtMinorVersion >= 8)
- qtProps.androidVersion = "1.6"; // Necessitas
- }
- return qtProps;
-}
-
-function makePluginData() {
- var pluginData = {};
- pluginData.type = undefined;
- pluginData.className = undefined;
- pluginData.autoLoad = true;
- pluginData["extends"] = [];
- return pluginData;
-}
-
-function makeQtModuleInfo(name, qbsName, deps) {
- var moduleInfo = {};
- moduleInfo.name = name; // As in the path to the headers and ".name" in the pri files.
- if (moduleInfo.name === undefined)
- moduleInfo.name = "";
- moduleInfo.qbsName = qbsName; // Lower-case version without "qt" prefix.
- moduleInfo.dependencies = deps || []; // qbs names.
- if (moduleInfo.qbsName && moduleInfo.qbsName !== "core"
- && !moduleInfo.dependencies.contains("core")) {
- moduleInfo.dependencies.unshift("core");
- }
- moduleInfo.isPrivate = qbsName && qbsName.endsWith("-private");
- moduleInfo.hasLibrary = !moduleInfo.isPrivate;
- moduleInfo.isStaticLibrary = false;
- moduleInfo.isPlugin = false;
- moduleInfo.mustExist = true;
- moduleInfo.modulePrefix = ""; // empty value means "Qt".
- moduleInfo.version = undefined;
- moduleInfo.includePaths = [];
- moduleInfo.compilerDefines = [];
- moduleInfo.staticLibrariesDebug = [];
- moduleInfo.staticLibrariesRelease = [];
- moduleInfo.dynamicLibrariesDebug = [];
- moduleInfo.dynamicLibrariesRelease = [];
- moduleInfo.linkerFlagsDebug = [];
- moduleInfo.linkerFlagsRelease = [];
- moduleInfo.libFilePathDebug = undefined;
- moduleInfo.libFilePathRelease = undefined;
- moduleInfo.frameworksDebug = [];
- moduleInfo.frameworksRelease = [];
- moduleInfo.frameworkPathsDebug = [];
- moduleInfo.frameworkPathsRelease = [];
- moduleInfo.libraryPaths = [];
- moduleInfo.libDir = "";
- moduleInfo.config = [];
- moduleInfo.supportedPluginTypes = [];
- moduleInfo.pluginData = makePluginData();
- return moduleInfo;
-}
-
-function frameworkHeadersPath(qtModuleInfo, qtProps) {
- return FileInfo.joinPaths(qtProps.libraryPath, qtModuleInfo.name + ".framework", "Headers");
-}
-
-function qt4ModuleIncludePaths(qtModuleInfo, qtProps) {
- var paths = [];
- if (isFramework(qtModuleInfo, qtProps))
- paths.push(frameworkHeadersPath(qtModuleInfo, qtProps));
- else
- paths.push(qtProps.includePath, FileInfo.joinPaths(qtProps.includePath, qtModuleInfo.name));
- return paths;
-}
-
-// We erroneously called the "testlib" module "test" for quite a while. Let's not punish users
-// for that.
-function addTestModule(modules) {
- var testModule = makeQtModuleInfo("QtTest", "test", ["testlib"]);
- testModule.hasLibrary = false;
- modules.push(testModule);
-}
-
-// See above.
-function addDesignerComponentsModule(modules) {
- var module = makeQtModuleInfo("QtDesignerComponents", "designercomponents",
- ["designercomponents-private"]);
- module.hasLibrary = false;
- modules.push(module);
-}
-
-function isFramework(modInfo, qtProps) {
- if (!qtProps.frameworkBuild || modInfo.isStaticLibrary)
- return false;
- var modulesNeverBuiltAsFrameworks = [
- "bootstrap", "openglextensions", "platformsupport", "qmldevtools", "harfbuzzng"
- ];
-
- if (qtProps.qtMajorVersion <= 5) {
- modulesNeverBuiltAsFrameworks.push("uitools"); // is framework since qt6
- }
-
- return !modulesNeverBuiltAsFrameworks.contains(modInfo.qbsName);
-}
-
-function libBaseName(modInfo, libName, debugBuild, qtProps) {
- var name = libName;
- if (qtProps.mkspecName.startsWith("win")) {
- if (debugBuild && needsDSuffix(qtProps))
- name += 'd';
- if (!modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5)
- name += qtProps.qtMajorVersion;
- }
- if (qtProps.mkspecName.contains("macx")
- || qtProps.mkspecName.contains("ios")
- || qtProps.mkspecName.contains("darwin")) {
- if (!isFramework(modInfo, qtProps)
- && qtProps.buildVariant.contains("debug")
- && (!qtProps.buildVariant.contains("release") || debugBuild)) {
- name += "_debug";
- }
- }
- return name;
-}
-
-function moduleNameWithoutPrefix(modInfo) {
- if (modInfo.name === "Phonon")
- return "phonon";
- if (!modInfo.modulePrefix && modInfo.name.startsWith("Qt"))
- return modInfo.name.slice(2); // Strip off "Qt".
- if (modInfo.name.startsWith(modInfo.modulePrefix))
- return modInfo.name.slice(modInfo.modulePrefix.length);
- return modInfo.name;
-}
-
-function libraryBaseName(modInfo, qtProps, debugBuild) {
- if (modInfo.isPlugin)
- return libBaseName(modInfo, modInfo.name, debugBuild, qtProps);
-
- // Some modules use a different naming scheme, so it doesn't get boring.
- var libNameBroken = modInfo.name === "Enginio"
- || modInfo.name === "DataVisualization"
- || modInfo.name === "Phonon";
-
- var libName = "";
- if (!modInfo.isExternal) {
- libName += !modInfo.modulePrefix && !libNameBroken ? "Qt" : modInfo.modulePrefix;
- if (qtProps.qtMajorVersion >= 5 && !isFramework(modInfo, qtProps) && !libNameBroken)
- libName += qtProps.qtMajorVersion;
- }
- libName += moduleNameWithoutPrefix(modInfo);
- if (!modInfo.isExternal)
- libName += qtProps.qtLibInfix;
- return libBaseName(modInfo, libName, debugBuild, qtProps);
-}
-
-function libNameForLinker(modInfo, qtProps, debugBuild) {
- if (!modInfo.hasLibrary)
- return undefined;
- var libName = libraryBaseName(modInfo, qtProps, debugBuild);
- if (qtProps.mkspecName.contains("msvc"))
- libName += ".lib";
- return libName;
-}
-
-function guessLibraryFilePath(prlFilePath, libDir, qtProps) {
- var baseName = FileInfo.baseName(prlFilePath);
- var prefixCandidates = ["", "lib"];
- var suffixCandidates = ["so." + qtProps.qtVersion, "so", "a", "lib", "dll.a"];
- for (var i = 0; i < prefixCandidates.length; ++i) {
- var prefix = prefixCandidates[i];
- for (var j = 0; j < suffixCandidates.length; ++j) {
- var suffix = suffixCandidates[j];
- var candidate = FileInfo.joinPaths(libDir, prefix + baseName + '.' + suffix);
- if (File.exists(candidate))
- return candidate;
- }
- }
-}
-
-function doReplaceQtLibNamesWithFilePath(namePathMap, libList) {
- for (var i = 0; i < libList.length; ++i) {
- var lib = libList[i];
- var path = namePathMap[lib];
- if (path)
- libList[i] = path;
- }
-}
-
-function replaceQtLibNamesWithFilePath(modules, qtProps) {
- // We don't want to add the libraries for Qt modules via "-l", because of the
- // danger that a wrong one will be picked up, e.g. from /usr/lib. Instead,
- // we pull them in using the full file path.
- var linkerNamesToFilePathsDebug = {};
- var linkerNamesToFilePathsRelease = {};
- for (var i = 0; i < modules.length; ++i) {
- var m = modules[i];
- linkerNamesToFilePathsDebug[libNameForLinker(m, qtProps, true)] = m.libFilePathDebug;
- linkerNamesToFilePathsRelease[libNameForLinker(m, qtProps, false)] = m.libFilePathRelease;
- }
- for (i = 0; i < modules.length; ++i) {
- var module = modules[i];
- doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsDebug, module.dynamicLibrariesDebug);
- doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsDebug, module.staticLibrariesDebug);
- doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsRelease,
- module.dynamicLibrariesRelease);
- doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsRelease,
- module.staticLibrariesRelease);
- }
-}
-
-function doSetupLibraries(modInfo, qtProps, debugBuild, nonExistingPrlFiles, androidAbi) {
- if (!modInfo.hasLibrary)
- return; // Can happen for Qt4 convenience modules, like "widgets".
-
- if (debugBuild) {
- if (!qtProps.buildVariant.contains("debug"))
- return;
- var modulesNeverBuiltAsDebug = ["bootstrap", "qmldevtools"];
- for (var i = 0; i < modulesNeverBuiltAsDebug.length; ++i) {
- var m = modulesNeverBuiltAsDebug[i];
- if (modInfo.qbsName === m || modInfo.qbsName === m + "-private")
- return;
- }
- } else if (!qtProps.buildVariant.contains("release")) {
- return;
- }
-
- var libs = modInfo.isStaticLibrary
- ? (debugBuild ? modInfo.staticLibrariesDebug : modInfo.staticLibrariesRelease)
- : (debugBuild ? modInfo.dynamicLibrariesDebug : modInfo.dynamicLibrariesRelease);
- var frameworks = debugBuild ? modInfo.frameworksDebug : modInfo.frameworksRelease;
- var frameworkPaths = debugBuild ? modInfo.frameworkPathsDebug : modInfo.frameworkPathsRelease;
- var flags = debugBuild ? modInfo.linkerFlagsDebug : modInfo.linkerFlagsRelease;
- var libFilePath;
-
- if (qtProps.mkspecName.contains("ios") && modInfo.isStaticLibrary) {
- libs.push("z", "m");
- if (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 8) {
- var platformSupportModule = makeQtModuleInfo("QtPlatformSupport", "platformsupport");
- libs.push(libNameForLinker(platformSupportModule, qtProps, debugBuild));
- }
- if (modInfo.name === "qios") {
- flags.push("-force_load", FileInfo.joinPaths(
- qtProps.pluginPath, "platforms",
- libBaseName(modInfo, "libqios", debugBuild, qtProps) + ".a"));
- }
- }
- var prlFilePath = modInfo.isPlugin
- ? FileInfo.joinPaths(qtProps.pluginPath, modInfo.pluginData.type)
- : (modInfo.libDir ? modInfo.libDir : qtProps.libraryPath);
- var libDir = prlFilePath;
- if (isFramework(modInfo, qtProps)) {
- prlFilePath = FileInfo.joinPaths(prlFilePath,
- libraryBaseName(modInfo, qtProps, false) + ".framework");
- libDir = prlFilePath;
- if (Utilities.versionCompare(qtProps.qtVersion, "5.14") >= 0)
- prlFilePath = FileInfo.joinPaths(prlFilePath, "Resources");
- }
- var baseName = libraryBaseName(modInfo, qtProps, debugBuild);
- if (!qtProps.mkspecName.startsWith("win") && !isFramework(modInfo, qtProps))
- baseName = "lib" + baseName;
- prlFilePath = FileInfo.joinPaths(prlFilePath, baseName);
- var isNonStaticQt4OnWindows = qtProps.mkspecName.startsWith("win")
- && !modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5;
- if (isNonStaticQt4OnWindows)
- prlFilePath = prlFilePath.slice(0, prlFilePath.length - 1); // The prl file base name does *not* contain the version number...
- // qt for android versions 6.0 and 6.1 don't have the architecture suffix in the prl file
- if (androidAbi.length > 0
- && modInfo.name !== "QtBootstrap"
- && (modInfo.name !== "QtQmlDevTools" || modInfo.name === "QtQmlDevTools"
- && Utilities.versionCompare(qtProps.qtVersion, "6.2") >= 0)
- && (Utilities.versionCompare(qtProps.qtVersion, "6.0") < 0
- || Utilities.versionCompare(qtProps.qtVersion, "6.2") >= 0)) {
- prlFilePath += "_";
- prlFilePath += androidAbi;
- }
-
- prlFilePath += ".prl";
-
- try {
- var prlFile = new TextFile(prlFilePath, TextFile.ReadOnly);
- while (!prlFile.atEof()) {
- var line = prlFile.readLine().trim();
- var equalsOffset = line.indexOf('=');
- if (equalsOffset === -1)
- continue;
- if (line.startsWith("QMAKE_PRL_TARGET")) {
- var isMingw = qtProps.mkspecName.startsWith("win")
- && qtProps.mkspecName.contains("g++");
- var isQtVersionBefore56 = qtProps.qtMajorVersion < 5
- || (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 6);
-
- // QMAKE_PRL_TARGET has a "lib" prefix, except for mingw.
- // Of course, the exception has an exception too: For static libs, mingw *does*
- // have the "lib" prefix.
- var libFileName = "";
- if (isQtVersionBefore56 && qtProps.qtMajorVersion === 5 && isMingw
- && !modInfo.isStaticLibrary) {
- libFileName += "lib";
- }
-
- libFileName += line.slice(equalsOffset + 1).trim();
- if (isNonStaticQt4OnWindows)
- libFileName += 4; // This is *not* part of QMAKE_PRL_TARGET...
- if (isQtVersionBefore56) {
- if (qtProps.mkspecName.contains("msvc")) {
- libFileName += ".lib";
- } else if (isMingw) {
- libFileName += ".a";
- if (!File.exists(FileInfo.joinPaths(libDir, libFileName)))
- libFileName = libFileName.slice(0, -2) + ".dll";
- }
- }
- libFilePath = FileInfo.joinPaths(libDir, libFileName);
- continue;
- }
- if (line.startsWith("QMAKE_PRL_CONFIG")) {
- modInfo.config = splitNonEmpty(line.slice(equalsOffset + 1).trim(), ' ');
- continue;
- }
- if (!line.startsWith("QMAKE_PRL_LIBS ="))
- continue;
-
- var parts = extractPaths(line.slice(equalsOffset + 1).trim(), prlFilePath);
- for (i = 0; i < parts.length; ++i) {
- var part = parts[i];
- var defaultInstallPrefix = "/Users/qt/work/qt/qtbase/build/target";
- part = part.replace("$$[QT_INSTALL_LIBS]", qtProps.libraryPath);
- part = part.replace("$$[QT_INSTALL_PLUGINS]", qtProps.pluginPath);
- part = part.replace("$$[QT_INSTALL_PREFIX]", qtProps.installPrefixPath);
- part = part.replace(defaultInstallPrefix, qtProps.installPrefixPath);
- if (part.startsWith("-l")) {
- libs.push(part.slice(2));
- } else if (part.startsWith("-L")) {
- modInfo.libraryPaths.push(part.slice(2));
- } else if (part.startsWith("-F")) {
- frameworkPaths.push(part.slice(2));
- } else if (part === "-framework") {
- if (++i < parts.length)
- frameworks.push(parts[i]);
- } else if (part === "-pthread") {
- // prl files for android have QMAKE_PRL_LIBS = -llog -pthread but the pthread
- // functionality is included in libc.
- if (androidAbi.length === 0)
- libs.push("pthread");
- } else if (part.startsWith('-')) { // Some other option
- console.debug("QMAKE_PRL_LIBS contains non-library option '" + part
- + "' in file '" + prlFilePath + "'");
- flags.push(part);
- } else if (part.startsWith("/LIBPATH:")) {
- libraryPaths.push(part.slice(9).replace(/\\/g, '/'));
- } else { // Assume it's a file path/name.
- libs.push(part.replace(/\\/g, '/'));
- }
- }
- }
- } catch (e) {
- // qt_ext_lib_extX.pri (usually) don't have a corresponding prl file.
- // So the pri file variable QMAKE_LIBS_LIBX points to the library
- if (modInfo.isExternal) {
- libFilePath = debugBuild ? modInfo.staticLibrariesDebug[0] :
- modInfo.staticLibrariesRelease[0];
- }
- if (!libFilePath || !File.exists(libFilePath))
- libFilePath = guessLibraryFilePath(prlFilePath, libDir, qtProps);
- if (nonExistingPrlFiles.contains(prlFilePath))
- return;
- nonExistingPrlFiles.push(prlFilePath);
- if (modInfo.mustExist) {
- console.warn("Could not open prl file '" + toNative(prlFilePath) + "' for module '"
- + modInfo.name
- + "' (" + e + "), and failed to deduce the library file path. "
- + " This module will likely not be usable by qbs.");
- }
- }
- finally {
- if (prlFile)
- prlFile.close();
- }
-
- if (debugBuild)
- modInfo.libFilePathDebug = libFilePath;
- else
- modInfo.libFilePathRelease = libFilePath;
-}
-
-function setupLibraries(qtModuleInfo, qtProps, nonExistingPrlFiles, androidAbi) {
- doSetupLibraries(qtModuleInfo, qtProps, true, nonExistingPrlFiles, androidAbi);
- doSetupLibraries(qtModuleInfo, qtProps, false, nonExistingPrlFiles, androidAbi);
-}
-
-function allQt4Modules(qtProps) {
- // as per http://doc.qt.io/qt-4.8/modules.html + private stuff.
- var modules = [];
-
- var core = makeQtModuleInfo("QtCore", "core");
- core.compilerDefines.push("QT_CORE_LIB");
- if (qtProps.qtNameSpace)
- core.compilerDefines.push("QT_NAMESPACE=" + qtProps.qtNameSpace);
- modules.push(core,
- makeQtModuleInfo("QtCore", "core-private", ["core"]),
- makeQtModuleInfo("QtGui", "gui"),
- makeQtModuleInfo("QtGui", "gui-private", ["gui"]),
- makeQtModuleInfo("QtMultimedia", "multimedia", ["gui", "network"]),
- makeQtModuleInfo("QtMultimedia", "multimedia-private", ["multimedia"]),
- makeQtModuleInfo("QtNetwork", "network"),
- makeQtModuleInfo("QtNetwork", "network-private", ["network"]),
- makeQtModuleInfo("QtOpenGL", "opengl", ["gui"]),
- makeQtModuleInfo("QtOpenGL", "opengl-private", ["opengl"]),
- makeQtModuleInfo("QtOpenVG", "openvg", ["gui"]),
- makeQtModuleInfo("QtScript", "script"),
- makeQtModuleInfo("QtScript", "script-private", ["script"]),
- makeQtModuleInfo("QtScriptTools", "scripttools", ["script", "gui"]),
- makeQtModuleInfo("QtScriptTools", "scripttools-private", ["scripttools"]),
- makeQtModuleInfo("QtSql", "sql"),
- makeQtModuleInfo("QtSql", "sql-private", ["sql"]),
- makeQtModuleInfo("QtSvg", "svg", ["gui"]),
- makeQtModuleInfo("QtSvg", "svg-private", ["svg"]),
- makeQtModuleInfo("QtWebKit", "webkit", ["gui", "network"]),
- makeQtModuleInfo("QtWebKit", "webkit-private", ["webkit"]),
- makeQtModuleInfo("QtXml", "xml"),
- makeQtModuleInfo("QtXml", "xml-private", ["xml"]),
- makeQtModuleInfo("QtXmlPatterns", "xmlpatterns", ["network"]),
- makeQtModuleInfo("QtXmlPatterns", "xmlpatterns-private", ["xmlpatterns"]),
- makeQtModuleInfo("QtDeclarative", "declarative", ["gui", "script"]),
- makeQtModuleInfo("QtDeclarative", "declarative-private", ["declarative"]),
- makeQtModuleInfo("QtDesigner", "designer", ["gui", "xml"]),
- makeQtModuleInfo("QtDesigner", "designer-private", ["designer"]),
- makeQtModuleInfo("QtUiTools", "uitools"),
- makeQtModuleInfo("QtUiTools", "uitools-private", ["uitools"]),
- makeQtModuleInfo("QtHelp", "help", ["network", "sql"]),
- makeQtModuleInfo("QtHelp", "help-private", ["help"]),
- makeQtModuleInfo("QtTest", "testlib"),
- makeQtModuleInfo("QtTest", "testlib-private", ["testlib"]));
- if (qtProps.mkspecName.startsWith("win")) {
- var axcontainer = makeQtModuleInfo("QAxContainer", "axcontainer");
- axcontainer.modulePrefix = "Q";
- axcontainer.isStaticLibrary = true;
- axcontainer.includePaths.push(FileInfo.joinPaths(qtProps.includePath, "ActiveQt"));
- modules.push(axcontainer);
-
- var axserver = makeQtModuleInfo("QAxServer", "axserver");
- axserver.modulePrefix = "Q";
- axserver.isStaticLibrary = true;
- axserver.compilerDefines.push("QAXSERVER");
- axserver.includePaths.push(FileInfo.joinPaths(qtProps.includePath, "ActiveQt"));
- modules.push(axserver);
- } else {
- modules.push(makeQtModuleInfo("QtDBus", "dbus"));
- modules.push(makeQtModuleInfo("QtDBus", "dbus-private", ["dbus"]));
- }
-
- var designerComponentsPrivate = makeQtModuleInfo(
- "QtDesignerComponents", "designercomponents-private",
- ["gui-private", "designer-private"]);
- designerComponentsPrivate.hasLibrary = true;
- modules.push(designerComponentsPrivate);
-
- var phonon = makeQtModuleInfo("Phonon", "phonon");
- phonon.includePaths = qt4ModuleIncludePaths(phonon, qtProps);
- modules.push(phonon);
-
- // Set up include paths that haven't been set up before this point.
- for (i = 0; i < modules.length; ++i) {
- var module = modules[i];
- if (module.includePaths.length > 0)
- continue;
- module.includePaths = qt4ModuleIncludePaths(module, qtProps);
- }
-
- // Set up compiler defines haven't been set up before this point.
- for (i = 0; i < modules.length; ++i) {
- module = modules[i];
- if (module.compilerDefines.length > 0)
- continue;
- module.compilerDefines.push("QT_" + module.qbsName.toUpperCase() + "_LIB");
- }
-
- // These are for the convenience of project file authors. It allows them
- // to add a dependency to e.g. "Qt.widgets" without a version check.
- var virtualModule = makeQtModuleInfo(undefined, "widgets", ["core", "gui"]);
- virtualModule.hasLibrary = false;
- modules.push(virtualModule);
- virtualModule = makeQtModuleInfo(undefined, "quick", ["declarative"]);
- virtualModule.hasLibrary = false;
- modules.push(virtualModule);
- virtualModule = makeQtModuleInfo(undefined, "concurrent");
- virtualModule.hasLibrary = false;
- modules.push(virtualModule);
- virtualModule = makeQtModuleInfo(undefined, "printsupport", ["core", "gui"]);
- virtualModule.hasLibrary = false;
- modules.push(virtualModule);
-
- addTestModule(modules);
- addDesignerComponentsModule(modules);
-
- var modulesThatCanBeDisabled = [
- "xmlpatterns", "multimedia", "phonon", "svg", "webkit", "script", "scripttools",
- "declarative", "gui", "dbus", "opengl", "openvg"];
- var nonExistingPrlFiles = [];
- for (i = 0; i < modules.length; ++i) {
- module = modules[i];
- var name = module.qbsName;
- var privateIndex = name.indexOf("-private");
- if (privateIndex !== -1)
- name = name.slice(0, privateIndex);
- if (modulesThatCanBeDisabled.contains(name))
- module.mustExist = false;
- if (qtProps.staticBuild)
- module.isStaticLibrary = true;
- setupLibraries(module, qtProps, nonExistingPrlFiles, "");
- }
- replaceQtLibNamesWithFilePath(modules, qtProps);
-
- return modules;
-}
-
-function getFileContentsRecursively(filePath) {
- var file = new TextFile(filePath, TextFile.ReadOnly);
- var lines = splitNonEmpty(file.readAll(), '\n');
- for (var i = 0; i < lines.length; ++i) {
- var includeString = "include(";
- var line = lines[i].trim();
- if (!line.startsWith(includeString))
- continue;
- var offset = includeString.length;
- var closingParenPos = line.indexOf(')', offset);
- if (closingParenPos === -1) {
- console.warn("Invalid include statement in '" + toNative(filePath) + "'");
- continue;
- }
- var includedFilePath = line.slice(offset, closingParenPos);
- if (!FileInfo.isAbsolutePath(includedFilePath))
- includedFilePath = FileInfo.joinPaths(FileInfo.path(filePath), includedFilePath);
- var includedContents = getFileContentsRecursively(includedFilePath);
- var j = i;
- for (var k = 0; k < includedContents.length; ++k)
- lines.splice(++j, 0, includedContents[k]);
- lines.splice(i--, 1);
- }
- file.close();
- return lines;
-}
-
-function extractPaths(rhs, filePath) {
- var paths = [];
- var startIndex = 0;
- for (;;) {
- while (startIndex < rhs.length && rhs.charAt(startIndex) === ' ')
- ++startIndex;
- if (startIndex >= rhs.length)
- break;
- var endIndex;
- if (rhs.charAt(startIndex) === '"') {
- ++startIndex;
- endIndex = rhs.indexOf('"', startIndex);
- if (endIndex === -1) {
- console.warn("Unmatched quote in file '" + toNative(filePath) + "'");
- break;
- }
- } else {
- endIndex = rhs.indexOf(' ', startIndex + 1);
- if (endIndex === -1)
- endIndex = rhs.length;
- }
- paths.push(FileInfo.cleanPath(rhs.slice(startIndex, endIndex)
- .replace("$$PWD", FileInfo.path(filePath))));
- startIndex = endIndex + 1;
- }
- return paths;
-}
-
-function removeDuplicatedDependencyLibs(modules) {
- var revDeps = {};
- var currentPath = [];
- var getLibraries;
- var getLibFilePath;
-
- function setupReverseDependencies(modules) {
- var moduleByName = {};
- for (var i = 0; i < modules.length; ++i)
- moduleByName[modules[i].qbsName] = modules[i];
- for (i = 0; i < modules.length; ++i) {
- var module = modules[i];
- for (var j = 0; j < module.dependencies.length; ++j) {
- var depmod = moduleByName[module.dependencies[j]];
- if (!depmod)
- continue;
- if (!revDeps[depmod.qbsName])
- revDeps[depmod.qbsName] = [];
- revDeps[depmod.qbsName].push(module);
- }
- }
- }
-
- function roots(modules) {
- var result = [];
- for (i = 0; i < modules.length; ++i) {
- var module = modules[i]
- if (module.dependencies.length === 0)
- result.push(module);
- }
- return result;
- }
-
- function traverse(module, libs) {
- if (currentPath.contains(module))
- return;
- currentPath.push(module);
- var moduleLibraryLists = getLibraries(module);
- for (var i = 0; i < moduleLibraryLists.length; ++i) {
- var modLibList = moduleLibraryLists[i];
- for (j = modLibList.length - 1; j >= 0; --j) {
- if (libs.contains(modLibList[j]))
- modLibList.splice(j, 1);
- }
- }
-
- var libFilePath = getLibFilePath(module);
- if (libFilePath)
- libs.push(libFilePath);
- for (i = 0; i < moduleLibraryLists.length; ++i)
- libs = libs.concat(moduleLibraryLists[i]);
- libs.sort();
-
- var deps = revDeps[module.qbsName];
- for (i = 0; i < (deps || []).length; ++i)
- traverse(deps[i], libs);
-
- currentPath.pop();
- }
-
- setupReverseDependencies(modules);
-
- // Traverse the debug variants of modules.
- getLibraries = function(module) {
- return [module.dynamicLibrariesDebug, module.staticLibrariesDebug];
- };
- getLibFilePath = function(module) { return module.libFilePathDebug; };
- var rootModules = roots(modules);
- for (var i = 0; i < rootModules.length; ++i)
- traverse(rootModules[i], []);
-
- // Traverse the release variants of modules.
- getLibraries = function(module) {
- return [module.dynamicLibrariesRelease, module.staticLibrariesRelease];
- };
- getLibFilePath = function(module) { return module.libFilePathRelease; };
- for (i = 0; i < rootModules.length; ++i)
- traverse(rootModules[i], []);
-}
-
-function allQt5Modules(qtProps, androidAbi) {
- var nonExistingPrlFiles = [];
- var modules = [];
- var modulesDir = FileInfo.joinPaths(qtProps.mkspecBasePath, "modules");
- var modulePriFiles = File.directoryEntries(modulesDir, File.Files);
- for (var i = 0; i < modulePriFiles.length; ++i) {
- var priFileName = modulePriFiles[i];
- var priFilePath = FileInfo.joinPaths(modulesDir, priFileName);
- var externalFileNamePrefix = "qt_ext_";
- var moduleFileNamePrefix = "qt_lib_";
- var pluginFileNamePrefix = "qt_plugin_";
- var moduleFileNameSuffix = ".pri";
- var fileHasExternalPrefix = priFileName.startsWith(externalFileNamePrefix);
- var fileHasModulePrefix = priFileName.startsWith(moduleFileNamePrefix);
- var fileHasPluginPrefix = priFileName.startsWith(pluginFileNamePrefix);
- if (!fileHasPluginPrefix && !fileHasModulePrefix && !fileHasExternalPrefix
- || !priFileName.endsWith(moduleFileNameSuffix)) {
- continue;
- }
- var moduleInfo = makeQtModuleInfo();
- moduleInfo.isPlugin = fileHasPluginPrefix;
- moduleInfo.isExternal = !moduleInfo.isPlugin && !fileHasModulePrefix;
- var fileNamePrefix = moduleInfo.isPlugin ? pluginFileNamePrefix : moduleInfo.isExternal
- ? externalFileNamePrefix : moduleFileNamePrefix;
- moduleInfo.qbsName = priFileName.slice(fileNamePrefix.length, -moduleFileNameSuffix.length);
- if (moduleInfo.isPlugin) {
- moduleInfo.name = moduleInfo.qbsName;
- moduleInfo.isStaticLibrary = true;
- }
- var moduleKeyPrefix = (moduleInfo.isPlugin ? "QT_PLUGIN" : "QT")
- + '.' + moduleInfo.qbsName + '.';
- moduleInfo.qbsName = moduleInfo.qbsName.replace("_private", "-private");
- var hasV2 = false;
- var hasModuleEntry = false;
- var lines = getFileContentsRecursively(priFilePath);
- if (moduleInfo.isExternal) {
- moduleInfo.name = "qt" + moduleInfo.qbsName;
- moduleInfo.isStaticLibrary = true;
- for (var k = 0; k < lines.length; ++k) {
- var extLine = lines[k].trim();
- var extFirstEqualsOffset = extLine.indexOf('=');
- if (extFirstEqualsOffset === -1)
- continue;
- var extKey = extLine.slice(0, extFirstEqualsOffset).trim();
- var extValue = extLine.slice(extFirstEqualsOffset + 1).trim();
- if (!extKey.startsWith("QMAKE_") || !extValue)
- continue;
-
- var elements = extKey.split('_');
- if (elements.length >= 3) {
- if (elements[1] === "LIBS") {
- extValue = extValue.replace("/home/qt/work/qt/qtbase/lib",
- qtProps.libraryPath);
- extValue = extValue.replace("$$[QT_INSTALL_LIBS]", qtProps.libraryPath);
- extValue = extValue.replace("$$[QT_INSTALL_LIBS/get]", qtProps.libraryPath);
- if (elements.length === 4 ) {
- if (elements[3] === androidAbi) {
- moduleInfo.staticLibrariesRelease.push(extValue);
- moduleInfo.staticLibrariesDebug.push(extValue);
- }
- } else if (elements.length === 5 ) {
- // That's for "x86_64"
- var abi = elements[3] + '_' + elements[4];
- if (abi === androidAbi) {
- moduleInfo.staticLibrariesRelease.push(extValue);
- moduleInfo.staticLibrariesDebug.push(extValue);
- }
- } else {
- moduleInfo.staticLibrariesRelease.push(extValue);
- moduleInfo.staticLibrariesDebug.push(extValue);
- }
- } else if (elements[1] === "INCDIR") {
- moduleInfo.includePaths.push(extValue.replace("$$[QT_INSTALL_HEADERS]",
- qtProps.includePath));
- }
- }
- }
- moduleInfo.compilerDefines.push("QT_" + moduleInfo.qbsName.toUpperCase() + "_LIB");
- moduleInfo.mustExist = false;
- } else {
- for (var j = 0; j < lines.length; ++j) {
- var line = lines[j].trim();
- var firstEqualsOffset = line.indexOf('=');
- if (firstEqualsOffset === -1)
- continue;
- var key = line.slice(0, firstEqualsOffset).trim();
- var value = line.slice(firstEqualsOffset + 1).trim();
- if (!key.startsWith(moduleKeyPrefix) || !value)
- continue;
- if (key.endsWith(".name")) {
- moduleInfo.name = value;
- } else if (key.endsWith(".module")) {
- hasModuleEntry = true;
- } else if (key.endsWith(".depends")) {
- moduleInfo.dependencies = splitNonEmpty(value, ' ');
- for (var k = 0; k < moduleInfo.dependencies.length; ++k) {
- moduleInfo.dependencies[k]
- = moduleInfo.dependencies[k].replace("_private", "-private");
- }
- } else if (key.endsWith(".module_config")) {
- var elems = splitNonEmpty(value, ' ');
- for (k = 0; k < elems.length; ++k) {
- var elem = elems[k];
- if (elem === "no_link")
- moduleInfo.hasLibrary = false;
- else if (elem === "staticlib")
- moduleInfo.isStaticLibrary = true;
- else if (elem === "internal_module")
- moduleInfo.isPrivate = true;
- else if (elem === "v2")
- hasV2 = true;
- }
- } else if (key.endsWith(".includes")) {
- moduleInfo.includePaths = extractPaths(value, priFilePath);
- for (k = 0; k < moduleInfo.includePaths.length; ++k) {
- moduleInfo.includePaths[k] = moduleInfo.includePaths[k]
- .replace("$$QT_MODULE_INCLUDE_BASE", qtProps.includePath)
- .replace("$$QT_MODULE_HOST_LIB_BASE", qtProps.hostLibraryPath)
- .replace("$$QT_MODULE_LIB_BASE", qtProps.libraryPath);
- }
- } else if (key.endsWith(".libs")) {
- var libDirs = extractPaths(value, priFilePath);
- if (libDirs.length === 1) {
- moduleInfo.libDir = libDirs[0]
- .replace("$$QT_MODULE_HOST_LIB_BASE", qtProps.hostLibraryPath)
- .replace("$$QT_MODULE_LIB_BASE", qtProps.libraryPath);
- } else {
- moduleInfo.libDir = qtProps.libraryPath;
- }
- } else if (key.endsWith(".DEFINES")) {
- moduleInfo.compilerDefines = splitNonEmpty(value, ' ');
- } else if (key.endsWith(".VERSION")) {
- moduleInfo.version = value;
- } else if (key.endsWith(".plugin_types")) {
- moduleInfo.supportedPluginTypes = splitNonEmpty(value, ' ');
- } else if (key.endsWith(".TYPE")) {
- moduleInfo.pluginData.type = value;
- } else if (key.endsWith(".EXTENDS")) {
- moduleInfo.pluginData["extends"] = splitNonEmpty(value, ' ');
- for (k = 0; k < moduleInfo.pluginData["extends"].length; ++k) {
- if (moduleInfo.pluginData["extends"][k] === "-") {
- moduleInfo.pluginData["extends"].splice(k, 1);
- moduleInfo.pluginData.autoLoad = false;
- break;
- }
- }
- } else if (key.endsWith(".CLASS_NAME")) {
- moduleInfo.pluginData.className = value;
- }
- }
- }
- if (hasV2 && !hasModuleEntry && !moduleInfo.isStaticLibrary)
- moduleInfo.hasLibrary = false;
-
- // Fix include paths for Apple frameworks.
- // The qt_lib_XXX.pri files contain wrong values for versions < 5.6.
- if (!hasV2 && isFramework(moduleInfo, qtProps)) {
- moduleInfo.includePaths = [];
- var baseIncDir = frameworkHeadersPath(moduleInfo, qtProps);
- if (moduleInfo.isPrivate) {
- baseIncDir = FileInfo.joinPaths(baseIncDir, moduleInfo.version);
- moduleInfo.includePaths.push(baseIncDir,
- FileInfo.joinPaths(baseIncDir, moduleInfo.name));
- } else {
- moduleInfo.includePaths.push(baseIncDir);
- }
- }
-
- setupLibraries(moduleInfo, qtProps, nonExistingPrlFiles, androidAbi);
-
- modules.push(moduleInfo);
- if (moduleInfo.qbsName === "testlib")
- addTestModule(modules);
- if (moduleInfo.qbsName === "designercomponents-private")
- addDesignerComponentsModule(modules);
- }
-
- replaceQtLibNamesWithFilePath(modules, qtProps);
- removeDuplicatedDependencyLibs(modules);
- return modules;
-}
-
function extractQbsArchs(module, qtProps) {
if (qtProps.mkspecName.startsWith("macx-")) {
var archs = [];
@@ -1335,7 +105,7 @@ function qbsTargetPlatformFromQtMkspec(qtProps) {
return "solaris";
if (mkspec.startsWith("vxworks-"))
return "vxworks";
- if (targetsDesktopWindows(qtProps) || mkspec.startsWith("winrt-"))
+ if (ProviderUtils.isDesktopWindowsQt(qtProps) || mkspec.startsWith("winrt-"))
return "windows";
}
@@ -1391,7 +161,7 @@ function defaultQpaPlugin(module, qtProps) {
function libraryFileTag(module, qtProps) {
if (module.isStaticLibrary)
return "staticlibrary";
- return isMsvcQt(qtProps) || qtProps.mkspecName.startsWith("win32-g++")
+ return ProviderUtils.isMsvcQt(qtProps) || qtProps.mkspecName.startsWith("win32-g++")
? "dynamiclibrary_import" : "dynamiclibrary";
}
@@ -1416,7 +186,20 @@ function findVariable(content, start) {
}
function minVersionJsString(minVersion) {
- return !minVersion ? "original" : ModUtils.toJSLiteral(minVersion);
+ return !minVersion ? "" : ModUtils.toJSLiteral(minVersion);
+}
+
+function abiToArchitecture(abi) {
+ switch (abi) {
+ case "armeabi-v7a":
+ return "armv7a";
+ case "arm64-v8a":
+ return "arm64";
+ case "x86":
+ case "x86_64":
+ default:
+ return abi;
+ }
}
function replaceSpecialValues(content, module, qtProps, abi) {
@@ -1447,7 +230,7 @@ function replaceSpecialValues(content, module, qtProps, abi) {
availableBuildVariants: ModUtils.toJSLiteral(qtProps.buildVariant),
staticBuild: ModUtils.toJSLiteral(qtProps.staticBuild),
frameworkBuild: ModUtils.toJSLiteral(qtProps.frameworkBuild),
- name: ModUtils.toJSLiteral(moduleNameWithoutPrefix(module)),
+ name: ModUtils.toJSLiteral(ProviderUtils.qtModuleNameWithoutPrefix(module)),
has_library: ModUtils.toJSLiteral(module.hasLibrary),
dependencies: ModUtils.toJSLiteral(module.dependencies),
includes: ModUtils.toJSLiteral(module.includePaths),
@@ -1464,18 +247,20 @@ function replaceSpecialValues(content, module, qtProps, abi) {
frameworksRelease: ModUtils.toJSLiteral(module.frameworksRelease),
libFilePathDebug: ModUtils.toJSLiteral(module.libFilePathDebug),
libFilePathRelease: ModUtils.toJSLiteral(module.libFilePathRelease),
- libNameForLinkerDebug: ModUtils.toJSLiteral(libNameForLinker(module, qtProps, true)),
+ libNameForLinkerDebug:
+ ModUtils.toJSLiteral(ProviderUtils.qtLibNameForLinker(module, qtProps, true)),
pluginTypes: ModUtils.toJSLiteral(module.supportedPluginTypes),
moduleConfig: ModUtils.toJSLiteral(module.config),
- libNameForLinkerRelease: ModUtils.toJSLiteral(libNameForLinker(module, qtProps, false)),
+ libNameForLinkerRelease:
+ ModUtils.toJSLiteral(ProviderUtils.qtLibNameForLinker(module, qtProps, false)),
entryPointLibsDebug: ModUtils.toJSLiteral(qtProps.entryPointLibsDebug),
entryPointLibsRelease: ModUtils.toJSLiteral(qtProps.entryPointLibsRelease),
- minWinVersion: minVersionJsString(qtProps.windowsVersion),
- minMacVersion: minVersionJsString(qtProps.macosVersion),
- minIosVersion: minVersionJsString(qtProps.iosVersion),
- minTvosVersion: minVersionJsString(qtProps.tvosVersion),
- minWatchosVersion: minVersionJsString(qtProps.watchosVersion),
- minAndroidVersion: minVersionJsString(qtProps.androidVersion),
+ minWinVersion_optional: minVersionJsString(qtProps.windowsVersion),
+ minMacVersion_optional: minVersionJsString(qtProps.macosVersion),
+ minIosVersion_optional: minVersionJsString(qtProps.iosVersion),
+ minTvosVersion_optional: minVersionJsString(qtProps.tvosVersion),
+ minWatchosVersion_optional: minVersionJsString(qtProps.watchosVersion),
+ minAndroidVersion_optional: minVersionJsString(qtProps.androidVersion),
};
var additionalContent = "";
@@ -1527,16 +312,15 @@ function replaceSpecialValues(content, module, qtProps, abi) {
+ indent + indent + "fileTags: [\"qt.core.metatypes\"]\n"
+ indent + "}";
}
- if (module.hasLibrary && !isFramework(module, qtProps)) {
+ if (module.hasLibrary && !ProviderUtils.qtIsFramework(module, qtProps)) {
if (additionalContent)
additionalContent += "\n" + indent;
additionalContent += "Group {\n";
if (module.isPlugin) {
additionalContent += indent + indent
- + "condition: Qt[\"" + module.qbsName + "\"].enableLinking\n";
+ + "condition: enableLinking\n";
}
- additionalContent += indent + indent + "files: [Qt[\"" + module.qbsName + "\"]"
- + ".libFilePath]\n"
+ additionalContent += indent + indent + "files: libFilePath\n"
+ indent + indent + "filesAreTargets: true\n"
+ indent + indent + "fileTags: [\"" + libraryFileTag(module, qtProps)
+ "\"]\n"
@@ -1546,9 +330,21 @@ function replaceSpecialValues(content, module, qtProps, abi) {
for (var pos = findVariable(content, 0); pos[0] !== -1;
pos = findVariable(content, pos[0])) {
- var replacement = dict[content.slice(pos[0] + 1, pos[1])] || "";
- content = content.slice(0, pos[0]) + replacement + content.slice(pos[1] + 1);
- pos[0] += replacement.length;
+ var varName = content.slice(pos[0] + 1, pos[1]);
+ var replacement = dict[varName] || "";
+ if (!replacement && varName.endsWith("_optional")) {
+ var prevNewline = content.lastIndexOf('\n', pos[0]);
+ if (prevNewline === -1)
+ prevNewline = 0;
+ var nextNewline = content.indexOf('\n', pos[0]);
+ if (nextNewline === -1)
+ prevNewline = content.length;
+ content = content.slice(0, prevNewline) + content.slice(nextNewline);
+ pos[0] = prevNewline;
+ } else {
+ content = content.slice(0, pos[0]) + replacement + content.slice(pos[1] + 1);
+ pos[0] += replacement.length;
+ }
}
return content;
}
@@ -1557,7 +353,7 @@ function copyTemplateFile(fileName, targetDirectory, qtProps, abi, location, all
pluginMap, nonEssentialPlugins)
{
if (!File.makePath(targetDirectory)) {
- throw "Cannot create directory '" + toNative(targetDirectory) + "'.";
+ throw "Cannot create directory '" + FileInfo.toNativeSeparators(targetDirectory) + "'.";
}
var sourceFile = new TextFile(FileInfo.joinPaths(location, "templates", fileName),
TextFile.ReadOnly);
@@ -1579,67 +375,64 @@ function copyTemplateFile(fileName, targetDirectory, qtProps, abi, location, all
targetFile.close();
}
-function setupOneQt(qmakeFilePath, outputBaseDir, uniquify, location) {
- if (!File.exists(qmakeFilePath))
- throw "The specified qmake file path '" + toNative(qmakeFilePath) + "' does not exist.";
- var qtProps = getQtProperties(qmakeFilePath);
- var androidAbis = [];
- if (qtProps.androidAbis !== undefined)
- // Multiple androidAbis detected: Qt >= 5.14
- androidAbis = qtProps.androidAbis;
- else
- // Single abi detected: Qt < 5.14
- androidAbis.push('');
- if (androidAbis.length > 1)
- console.info("Qt with multiple abi detected: '" + androidAbis + "'");
+function setupOneQt(moduleName, qtInfo, outputBaseDir, uniquify, location) {
+ var qtProps = qtInfo.qtProps;
var relativeSearchPaths = [];
- for (a = 0; a < androidAbis.length; ++a) {
- if (androidAbis.length > 1)
- console.info("Configuring abi '" + androidAbis[a] + "'...");
- var modules = qtProps.qtMajorVersion < 5 ? allQt4Modules(qtProps) :
- allQt5Modules(qtProps,androidAbis[a]);
- var pluginsByType = {};
- var nonEssentialPlugins = [];
- for (var i = 0; i < modules.length; ++i) {
- var m = modules[i];
- if (m.isPlugin) {
- if (!pluginsByType[m.pluginData.type])
- pluginsByType[m.pluginData.type] = [];
- pluginsByType[m.pluginData.type].push(m.name);
- if (!m.pluginData.autoLoad)
- nonEssentialPlugins.push(m.name);
- }
- }
-
- var relativeSearchPath = uniquify ? Utilities.getHash(qmakeFilePath) : "";
- relativeSearchPath = FileInfo.joinPaths(relativeSearchPath, androidAbis[a]);
+ for (a = 0; a < qtInfo.abiInfos.length; ++a) {
+ var abiInfo = qtInfo.abiInfos[a];
+ var androidAbi = abiInfo.androidAbi;
+ if (qtInfo.abiInfos.length > 1)
+ console.info("Configuring abi '" + androidAbi + "'...");
+
+ var relativeSearchPath = uniquify ? Utilities.getHash(qtInfo.qmakeFilePath) : "";
+ relativeSearchPath = FileInfo.joinPaths(relativeSearchPath, androidAbi);
var qbsQtModuleBaseDir = FileInfo.joinPaths(outputBaseDir, relativeSearchPath,
"modules", "Qt");
- if (File.exists(qbsQtModuleBaseDir))
- File.remove(qbsQtModuleBaseDir);
+ // TODO:
+ // if (File.exists(qbsQtModuleBaseDir))
+ // File.remove(qbsQtModuleBaseDir);
var allFiles = [];
- copyTemplateFile("QtModule.qbs", qbsQtModuleBaseDir, qtProps, androidAbis[a], location,
- allFiles);
- copyTemplateFile("QtPlugin.qbs", qbsQtModuleBaseDir, qtProps, androidAbis[a], location,
- allFiles);
- copyTemplateFile("plugin_support.qbs",
- FileInfo.joinPaths(qbsQtModuleBaseDir, "plugin_support"), qtProps,
- androidAbis[a], location, allFiles, undefined, pluginsByType,
- nonEssentialPlugins);
+ if (moduleName === "plugin_support") {
+ copyTemplateFile("plugin_support.qbs",
+ FileInfo.joinPaths(qbsQtModuleBaseDir, "plugin_support"), qtProps,
+ androidAbi, location, allFiles, undefined, abiInfo.pluginsByType,
+ abiInfo.nonEssentialPlugins);
+ relativeSearchPaths.push(relativeSearchPath);
+ return relativeSearchPaths;
+ } else if (moduleName === "android_support") {
+ // Note that it's not strictly necessary to copy this one, as it has no variable content.
+ // But we do it anyway for consistency.
+ copyTemplateFile("android_support.qbs",
+ FileInfo.joinPaths(qbsQtModuleBaseDir, "android_support"),
+ qtProps, androidAbi, location, allFiles);
+ relativeSearchPaths.push(relativeSearchPath);
+ return relativeSearchPaths;
+ } else if (moduleName === "qmlcache") {
+ var qmlcacheStr = "qmlcache";
+ if (File.exists(FileInfo.joinPaths(qtProps.qmlLibExecPath,
+ "qmlcachegen" + FileInfo.executableSuffix()))) {
+ copyTemplateFile(qmlcacheStr + ".qbs",
+ FileInfo.joinPaths(qbsQtModuleBaseDir, qmlcacheStr), qtProps,
+ androidAbi, location, allFiles);
+ }
+ relativeSearchPaths.push(relativeSearchPath);
+ return relativeSearchPaths;
+ }
- for (i = 0; i < modules.length; ++i) {
- var module = modules[i];
+ if (abiInfo.modules[moduleName] !== undefined) {
+ var module = abiInfo.modules[moduleName];
var qbsQtModuleDir = FileInfo.joinPaths(qbsQtModuleBaseDir, module.qbsName);
var moduleTemplateFileName;
+
if (module.qbsName === "core") {
moduleTemplateFileName = "core.qbs";
- copyTemplateFile("moc.js", qbsQtModuleDir, qtProps, androidAbis[a], location,
+ copyTemplateFile("moc.js", qbsQtModuleDir, qtProps, androidAbi, location,
allFiles);
- copyTemplateFile("qdoc.js", qbsQtModuleDir, qtProps, androidAbis[a], location,
+ copyTemplateFile("qdoc.js", qbsQtModuleDir, qtProps, androidAbi, location,
allFiles);
- copyTemplateFile("rcc.js", qbsQtModuleDir, qtProps, androidAbis[a], location,
+ copyTemplateFile("rcc.js", qbsQtModuleDir, qtProps, androidAbi, location,
allFiles);
} else if (module.qbsName === "gui") {
moduleTemplateFileName = "gui.qbs";
@@ -1647,62 +440,51 @@ function setupOneQt(qmakeFilePath, outputBaseDir, uniquify, location) {
moduleTemplateFileName = "scxml.qbs";
} else if (module.qbsName === "dbus") {
moduleTemplateFileName = "dbus.qbs";
- copyTemplateFile("dbus.js", qbsQtModuleDir, qtProps, androidAbis[a], location,
+ copyTemplateFile("dbus.js", qbsQtModuleDir, qtProps, androidAbi, location,
allFiles);
} else if (module.qbsName === "qml") {
moduleTemplateFileName = "qml.qbs";
- copyTemplateFile("qml.js", qbsQtModuleDir, qtProps, androidAbis[a], location,
+ copyTemplateFile("qml.js", qbsQtModuleDir, qtProps, androidAbi, location,
allFiles);
- var qmlcacheStr = "qmlcache";
- if (File.exists(FileInfo.joinPaths(qtProps.qmlLibExecPath,
- "qmlcachegen" + FileInfo.executableSuffix()))) {
- copyTemplateFile(qmlcacheStr + ".qbs",
- FileInfo.joinPaths(qbsQtModuleBaseDir, qmlcacheStr), qtProps,
- androidAbis[a], location, allFiles);
- }
} else if (module.qbsName === "quick") {
moduleTemplateFileName = "quick.qbs";
- copyTemplateFile("quick.js", qbsQtModuleDir, qtProps, androidAbis[a], location,
+ copyTemplateFile("quick.js", qbsQtModuleDir, qtProps, androidAbi, location,
allFiles);
- copyTemplateFile("rcc.js", qbsQtModuleDir, qtProps, androidAbis[a], location,
+ copyTemplateFile("rcc.js", qbsQtModuleDir, qtProps, androidAbi, location,
allFiles);
} else if (module.isPlugin) {
moduleTemplateFileName = "plugin.qbs";
} else {
moduleTemplateFileName = "module.qbs";
}
- copyTemplateFile(moduleTemplateFileName, qbsQtModuleDir, qtProps, androidAbis[a],
+ copyTemplateFile(moduleTemplateFileName, qbsQtModuleDir, qtProps, androidAbi,
location, allFiles, module);
+ relativeSearchPaths.push(relativeSearchPath);
}
-
- // Note that it's not strictly necessary to copy this one, as it has no variable content.
- // But we do it anyway for consistency.
- copyTemplateFile("android_support.qbs",
- FileInfo.joinPaths(qbsQtModuleBaseDir, "android_support"),
- qtProps, androidAbis[a], location, allFiles);
- relativeSearchPaths.push(relativeSearchPath)
}
return relativeSearchPaths;
}
-function doSetup(qmakeFilePaths, outputBaseDir, location) {
- qmakeFilePaths = getQmakeFilePaths(qmakeFilePaths);
- if (!qmakeFilePaths || qmakeFilePaths.length === 0)
+function doSetup(moduleName, qtInfos, outputBaseDir, location) {
+ if (!qtInfos || qtInfos.length === 0)
return [];
- var uniquifySearchPath = qmakeFilePaths.length > 1;
+ var uniquifySearchPath = qtInfos.length > 1;
var allSearchPaths = [];
- for (var i = 0; i < qmakeFilePaths.length; ++i) {
+ moduleName = moduleName.substring(3);
+ for (var i = 0; i < qtInfos.length; ++i) {
try {
- console.info("Setting up Qt at '" + toNative(qmakeFilePaths[i]) + "'...");
- var searchPaths = setupOneQt(qmakeFilePaths[i], outputBaseDir, uniquifySearchPath,
+ console.info("Setting up Qt module '" + moduleName + "' for Qt located at '"
+ + FileInfo.toNativeSeparators(qtInfos[i].qmakeFilePath) + "'.");
+ var searchPaths = setupOneQt(moduleName, qtInfos[i], outputBaseDir, uniquifySearchPath,
location);
if (searchPaths.length > 0) {
for (var j = 0; j < searchPaths.length; ++j )
allSearchPaths.push(searchPaths[j]);
- console.info("Qt was set up successfully.");
}
} catch (e) {
- console.warn("Error setting up Qt for '" + toNative(qmakeFilePaths[i]) + "': " + e);
+ console.warn("Error setting up Qt module '" + moduleName + "' for '"
+ + FileInfo.toNativeSeparators(qtInfos[i].qmakeFilePath) + "': " + e);
+ throw e;
}
}
return allSearchPaths;
diff --git a/share/qbs/module-providers/Qt/templates/core.qbs b/share/qbs/module-providers/Qt/templates/core.qbs
index b980588ff..485402716 100644
--- a/share/qbs/module-providers/Qt/templates/core.qbs
+++ b/share/qbs/module-providers/Qt/templates/core.qbs
@@ -76,6 +76,8 @@ Module {
property string qtBuildVariant: {
if (availableBuildVariants.contains(qbs.buildVariant))
return qbs.buildVariant;
+ if (qbs.buildVariant === "profiling" && availableBuildVariants.contains("release"))
+ return "release";
return availableBuildVariants.length > 0 ? availableBuildVariants[0] : "";
}
@@ -107,6 +109,7 @@ Module {
property string libFilePathRelease: @libFilePathRelease@
property string libFilePath: qtBuildVariant === "debug"
? libFilePathDebug : libFilePathRelease
+ property bool useRPaths: qbs.targetOS.contains("linux") && !qbs.targetOS.contains("android")
property stringList coreLibPaths: @libraryPaths@
property bool hasLibrary: true
@@ -196,8 +199,7 @@ Module {
return undefined;
return frameworks;
}
- cpp.rpaths: qbs.targetOS.contains('linux') && !qbs.targetOS.contains("android") ? [libPath] :
- undefined
+ cpp.rpaths: useRPaths ? libPath : undefined
cpp.runtimeLibrary: qbs.toolchain.contains("msvc")
? config.contains("static_runtime") ? "static" : "dynamic"
: original
@@ -220,12 +222,12 @@ Module {
return "libc++";
return original;
}
- cpp.minimumWindowsVersion: @minWinVersion@
- cpp.minimumMacosVersion: @minMacVersion@
- cpp.minimumIosVersion: @minIosVersion@
- cpp.minimumTvosVersion: @minTvosVersion@
- cpp.minimumWatchosVersion: @minWatchosVersion@
- cpp.minimumAndroidVersion: @minAndroidVersion@
+ cpp.minimumWindowsVersion: @minWinVersion_optional@
+ cpp.minimumMacosVersion: @minMacVersion_optional@
+ cpp.minimumIosVersion: @minIosVersion_optional@
+ cpp.minimumTvosVersion: @minTvosVersion_optional@
+ cpp.minimumWatchosVersion: @minWatchosVersion_optional@
+ cpp.minimumAndroidVersion: @minAndroidVersion_optional@
// Universal Windows Platform support
cpp.windowsApiFamily: mkspecName.startsWith("winrt-") ? "pc" : undefined
diff --git a/share/qbs/module-providers/Qt/templates/dbus.qbs b/share/qbs/module-providers/Qt/templates/dbus.qbs
index 08e1e49e7..bbda5a4f2 100644
--- a/share/qbs/module-providers/Qt/templates/dbus.qbs
+++ b/share/qbs/module-providers/Qt/templates/dbus.qbs
@@ -1,6 +1,5 @@
import qbs.FileInfo
import qbs.ModUtils
-import "../QtModule.qbs" as QtModule
import "dbus.js" as DBus
QtModule {
diff --git a/share/qbs/module-providers/Qt/templates/gui.qbs b/share/qbs/module-providers/Qt/templates/gui.qbs
index 6ba5e48c4..db491eafe 100644
--- a/share/qbs/module-providers/Qt/templates/gui.qbs
+++ b/share/qbs/module-providers/Qt/templates/gui.qbs
@@ -1,7 +1,6 @@
import qbs.FileInfo
import qbs.ModUtils
import qbs.Utilities
-import '../QtModule.qbs' as QtModule
QtModule {
qtModuleName: "Gui"
diff --git a/share/qbs/module-providers/Qt/templates/module.qbs b/share/qbs/module-providers/Qt/templates/module.qbs
index ccafe4122..9f0313ab5 100644
--- a/share/qbs/module-providers/Qt/templates/module.qbs
+++ b/share/qbs/module-providers/Qt/templates/module.qbs
@@ -1,5 +1,3 @@
-import '../QtModule.qbs' as QtModule
-
QtModule {
qtModuleName: @name@
Depends { name: "Qt"; submodules: @dependencies@}
diff --git a/share/qbs/module-providers/Qt/templates/plugin.qbs b/share/qbs/module-providers/Qt/templates/plugin.qbs
index e73e2a4d9..34d4f4153 100644
--- a/share/qbs/module-providers/Qt/templates/plugin.qbs
+++ b/share/qbs/module-providers/Qt/templates/plugin.qbs
@@ -1,5 +1,3 @@
-import '../QtPlugin.qbs' as QtPlugin
-
QtPlugin {
qtModuleName: @name@
Depends { name: "Qt"; submodules: @dependencies@}
diff --git a/share/qbs/module-providers/Qt/templates/qml.qbs b/share/qbs/module-providers/Qt/templates/qml.qbs
index de1318695..0a938e58d 100644
--- a/share/qbs/module-providers/Qt/templates/qml.qbs
+++ b/share/qbs/module-providers/Qt/templates/qml.qbs
@@ -1,7 +1,6 @@
import qbs.FileInfo
import qbs.Host
import qbs.TextFile
-import '../QtModule.qbs' as QtModule
import "qml.js" as Qml
QtModule {
diff --git a/share/qbs/module-providers/Qt/templates/quick.qbs b/share/qbs/module-providers/Qt/templates/quick.qbs
index 27f49cbd6..5fc4aa349 100644
--- a/share/qbs/module-providers/Qt/templates/quick.qbs
+++ b/share/qbs/module-providers/Qt/templates/quick.qbs
@@ -33,7 +33,6 @@ import qbs.FileInfo
import qbs.Process
import qbs.TextFile
import qbs.Utilities
-import '../QtModule.qbs' as QtModule
import 'quick.js' as QC
QtModule {
diff --git a/share/qbs/module-providers/Qt/templates/scxml.qbs b/share/qbs/module-providers/Qt/templates/scxml.qbs
index b800dfe70..757041b52 100644
--- a/share/qbs/module-providers/Qt/templates/scxml.qbs
+++ b/share/qbs/module-providers/Qt/templates/scxml.qbs
@@ -1,6 +1,5 @@
import qbs.FileInfo
import qbs.Utilities
-import "../QtModule.qbs" as QtModule
QtModule {
qtModuleName: "Scxml"
diff --git a/share/qbs/module-providers/__fallback/fallback.qbs b/share/qbs/module-providers/__fallback/fallback.qbs
index 52a3f080a..349af3a52 100644
--- a/share/qbs/module-providers/__fallback/fallback.qbs
+++ b/share/qbs/module-providers/__fallback/fallback.qbs
@@ -46,6 +46,8 @@ Module {
property string theName: FileInfo.completeBaseName(filePath)
+ readonly property bool __fallback: true // Hack, please look away
+
Probes.PkgConfigProbe {
id: pkgConfigProbe
condition: pkgconfig.present
diff --git a/share/qbs/module-providers/qbspkgconfig.qbs b/share/qbs/module-providers/qbspkgconfig.qbs
index 2736220a8..885a6da7a 100644
--- a/share/qbs/module-providers/qbspkgconfig.qbs
+++ b/share/qbs/module-providers/qbspkgconfig.qbs
@@ -40,51 +40,54 @@
import qbs.Environment
import qbs.File
import qbs.FileInfo
+import qbs.Host
import qbs.ModUtils
import qbs.PkgConfig
+import qbs.ProviderUtils
+import qbs.Probes
import qbs.Process
import qbs.TextFile
import "Qt/setup-qt.js" as SetupQt
ModuleProvider {
- property string executableFilePath
+ property stringList executableNames: ["pkgconf", "pkg-config"]
+ property string executableFilePath: pkgConfigProbe.filePath
property stringList extraPaths
property stringList libDirs
property bool staticMode: false
+ property bool definePrefix: Host.os().includes("windows")
// We take the sysroot default from qbs.sysroot, except for Xcode toolchains, where
// the sysroot points into the Xcode installation and does not contain .pc files.
property path sysroot: qbs.toolchain && qbs.toolchain.includes("xcode")
? undefined : qbs.sysroot
- // TODO: deprecate in 2.2, remove in 2.3
- property bool mergeDependencies: false
-
- relativeSearchPaths: {
+ Probes.BinaryProbe {
+ id: pkgConfigProbe
+ condition: !executableFilePath
+ names: executableNames
+ }
- function exeSuffix(qbs) { return FileInfo.executableSuffix(); }
+ Probes.QbsPkgConfigProbe {
+ id: theProbe
+ _executableFilePath: executableFilePath
+ _extraPaths: extraPaths
+ _sysroot: sysroot
+ _libDirs: libDirs
+ _staticMode: staticMode
+ _definePrefix: definePrefix
+ }
- // we need Probes in Providers...
- function getPkgConfigExecutable(qbs) {
- function splitNonEmpty(s, c) { return s.split(c).filter(function(e) { return e; }) }
+ Probes.QmakeProbe {
+ id: qmakeProbe
+ condition: moduleName.startsWith("Qt") && theProbe.qmakePaths
+ qmakePaths: theProbe.qmakePaths
+ }
- var pathValue = Environment.getEnv("PATH");
- if (!pathValue)
- return undefined;
- var dirs = splitNonEmpty(pathValue, FileInfo.pathListSeparator());
- var suffix = exeSuffix(qbs);
- var filePaths = [];
- for (var i = 0; i < dirs.length; ++i) {
- var candidate = FileInfo.joinPaths(dirs[i], "pkg-config" + suffix);
- var canonicalCandidate = FileInfo.canonicalPath(candidate);
- if (!canonicalCandidate || !File.exists(canonicalCandidate))
- continue;
- return canonicalCandidate;
- }
- return undefined;
- }
+ isEager: false
+ relativeSearchPaths: {
function getModuleInfo(pkg, staticMode) {
var result = {};
@@ -126,14 +129,12 @@ ModuleProvider {
return result;
}
- function getModuleName(packageName) { return packageName.replace(/\./g, '-'); }
-
function getModuleDependencies(pkg, staticMode) {
var mapper = function(p) {
var result = {};
for (var key in p)
result[key] = p[key];
- result.name = getModuleName(result.name);
+ result.name = ProviderUtils.pkgConfigToModuleName(result.name);
return result;
}
var result = pkg.requires.map(mapper);
@@ -142,138 +143,83 @@ ModuleProvider {
return result;
}
- console.debug("Running pkgconfig provider.")
+ console.debug("Running pkgconfig provider for " + moduleName + ".");
var outputDir = FileInfo.joinPaths(outputBaseDir, "modules");
File.makePath(outputDir);
- var options = {};
- options.libDirs = libDirs;
- options.sysroot = sysroot;
- if (options.sysroot)
- options.allowSystemLibraryPaths = true;
- options.staticMode = staticMode;
- options.mergeDependencies = mergeDependencies;
- options.extraPaths = extraPaths;
- if (options.sysroot && !options.libDirs) {
- options.libDirs = [
- sysroot + "/usr/lib/pkgconfig",
- sysroot + "/usr/share/pkgconfig"
- ];
- }
- if (!options.libDirs) {
- // if we have pkg-config installed, let's ask it for its search paths (since
- // built-in search paths can differ between platforms)
- var executable = executableFilePath ? executableFilePath : getPkgConfigExecutable(qbs);
- if (executable) {
- var p = new Process()
- if (p.exec(executable, ['pkg-config', '--variable=pc_path']) === 0) {
- var stdout = p.readStdOut().trim();
- // TODO: pathListSeparator? depends on what pkg-config prints on Windows
- options.libDirs = stdout ? stdout.split(':'): [];
- }
- }
+ var moduleMapping = {
+ "protobuf": "protobuflib"
}
-
- function setupQt(pkg) {
- var packageName = pkg.baseFileName;
- if (packageName === "QtCore"
- || packageName === "Qt5Core"
- || packageName === "Qt6Core") {
- var binDir = pkg.variables["bindir"] || pkg.variables["host_bins"];
- if (!binDir) {
- if (packageName === "QtCore") { // Qt4 does not have host_bins
- var mocLocation = pkg.variables["moc_location"];
- if (!mocLocation) {
- console.warn("No moc_location variable in " + packageName);
- return;
- }
- binDir = FileInfo.path(mocLocation);
- } else {
- console.warn("No 'bindir' or 'host_bins' variable in " + packageName);
- return;
- }
- }
- var suffix = exeSuffix(qbs);
- var qmakePaths = [FileInfo.joinPaths(binDir, "qmake" + suffix)];
+ var reverseMapping = {}
+ for (var key in moduleMapping)
+ reverseMapping[moduleMapping[key]] = key
+
+ if (moduleName.startsWith("Qt")) {
+ function setupQt(packageName, qtInfos) {
+ if (qtInfos === undefined || qtInfos.length === 0)
+ return [];
var qtProviderDir = FileInfo.joinPaths(path, "Qt");
- SetupQt.doSetup(qmakePaths, outputBaseDir, qtProviderDir, qbs);
+ return SetupQt.doSetup(packageName, qtInfos, outputBaseDir, qtProviderDir);
}
- }
- var moduleMapping = {
- "protobuf": "protobuflib",
- "grpc++": "grpcpp"
+ if (!sysroot) {
+ return setupQt(moduleName, qmakeProbe.qtInfos);
+ }
+ return [];
}
- var pkgConfig = new PkgConfig(options);
-
- var brokenPackages = [];
- var packages = pkgConfig.packages();
- for (var packageName in packages) {
- var pkg = packages[packageName];
- if (pkg.isBroken) {
- brokenPackages.push(pkg);
- continue;
- }
- if (packageName.startsWith("Qt")) {
- if (!sysroot) {
- setupQt(pkg);
- }
- continue;
- }
- var moduleName = getModuleName(moduleMapping[packageName]
- ? moduleMapping[packageName]
- : packageName);
- var moduleInfo = getModuleInfo(pkg, staticMode);
- var deps = getModuleDependencies(pkg, staticMode);
+ var pkg;
+ pkg = theProbe.packages[reverseMapping[moduleName]];
+ if (pkg === undefined)
+ pkg = theProbe.packagesByModuleName[moduleName];
+ if (pkg === undefined)
+ return [];
- var moduleDir = FileInfo.joinPaths(outputDir, moduleName);
- File.makePath(moduleDir);
- var module =
- new TextFile(FileInfo.joinPaths(moduleDir, "module.qbs"), TextFile.WriteOnly);
- module.writeLine("Module {");
- module.writeLine(" version: " + ModUtils.toJSLiteral(moduleInfo.version));
- module.writeLine(" Depends { name: 'cpp' }");
- deps.forEach(function(dep) {
- var depName = getModuleName(
- moduleMapping[dep.name] ? moduleMapping[dep.name] : dep.name);
- module.write(" Depends { name: '" + depName + "'");
- for (var k in dep) {
- if (k === "name")
- continue;
- module.write("; " + k + ": " + ModUtils.toJSLiteral(dep[k]));
- }
- module.writeLine(" }");
- })
- function writeProperty(propertyName) {
- var value = moduleInfo[propertyName];
- if (value.length !== 0) { // skip empty props for simplicity of the module file
- module.writeLine(
- " cpp." + propertyName + ":" + ModUtils.toJSLiteral(value));
- }
- }
- writeProperty("includePaths");
- writeProperty("systemIncludePaths");
- writeProperty("defines");
- writeProperty("commonCompilerFlags");
- writeProperty("dynamicLibraries");
- writeProperty("staticLibraries");
- writeProperty("libraryPaths");
- writeProperty("frameworks");
- writeProperty("frameworkPaths");
- writeProperty("driverLinkerFlags");
- module.writeLine("}");
- module.close();
+ if (pkg.isBroken) {
+ console.warn("Failed to load " + moduleName + " as it's pkg-config package is broken");
+ return [];
}
-
- if (brokenPackages.length !== 0) {
- console.warn("Failed to load some pkg-config packages:");
- for (var i = 0; i < brokenPackages.length; ++i) {
- console.warn(" " + brokenPackages[i].filePath
- + ": " + brokenPackages[i].errorText);
+ var moduleInfo = getModuleInfo(pkg, staticMode);
+ var deps = getModuleDependencies(pkg, staticMode);
+
+ var moduleDir = FileInfo.joinPaths(outputDir, moduleName);
+ File.makePath(moduleDir);
+ var module =
+ new TextFile(FileInfo.joinPaths(moduleDir, "module.qbs"), TextFile.WriteOnly);
+ module.writeLine("Module {");
+ module.writeLine(" version: " + ModUtils.toJSLiteral(moduleInfo.version));
+ module.writeLine(" Depends { name: 'cpp' }");
+ deps.forEach(function(dep) {
+ var depName = ProviderUtils.pkgConfigToModuleName(
+ moduleMapping[dep.name] ? moduleMapping[dep.name] : dep.name);
+ module.write(" Depends { name: '" + depName + "'");
+ for (var k in dep) {
+ if (k === "name")
+ continue;
+ module.write("; " + k + ": " + ModUtils.toJSLiteral(dep[k]));
+ }
+ module.writeLine(" }");
+ })
+ function writeProperty(propertyName) {
+ var value = moduleInfo[propertyName];
+ if (value.length !== 0) { // skip empty props for simplicity of the module file
+ module.writeLine(
+ " cpp." + propertyName + ":" + ModUtils.toJSLiteral(value));
}
}
+ writeProperty("includePaths");
+ writeProperty("systemIncludePaths");
+ writeProperty("defines");
+ writeProperty("commonCompilerFlags");
+ writeProperty("dynamicLibraries");
+ writeProperty("staticLibraries");
+ writeProperty("libraryPaths");
+ writeProperty("frameworks");
+ writeProperty("frameworkPaths");
+ writeProperty("driverLinkerFlags");
+ module.writeLine("}");
+ module.close();
return "";
}
diff --git a/share/qbs/modules/Exporter/cmake/cmakeexporter.js b/share/qbs/modules/Exporter/cmake/cmakeexporter.js
new file mode 100644
index 000000000..093032f2e
--- /dev/null
+++ b/share/qbs/modules/Exporter/cmake/cmakeexporter.js
@@ -0,0 +1,239 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Raphaël Cotty <raphael.cotty@gmail.com>
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of the 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.
+**
+****************************************************************************/
+
+var FileInfo = require("qbs.FileInfo");
+var ModUtils = require("qbs.ModUtils");
+var ExporterHelpers = require("../exporter.js");
+
+function tagListToString(tagList)
+{
+ return JSON.stringify(tagList);
+}
+
+function collectAutodetectedData(project, topLevelProduct, outputs)
+{
+ var packageName = topLevelProduct.Exporter.cmake.packageName;
+
+ var data = {};
+ data.packageName = packageName;
+ data.installPrefixDir = "_" + packageName.toUpperCase() + "_INSTALL_PREFIX";
+ data.packages = [];
+
+ function quote(value)
+ {
+ return "\"" + value + "\"";
+ }
+
+ function quoteAndPrefixify(propName)
+ {
+ function quoteAndPrefixifyHelper(value) {
+ var prefixToStrip =
+ ExporterHelpers.getPrefixToStrip(project, topLevelProduct, propName, value);
+ if (typeof value !== "string"
+ || !prefixToStrip
+ || (value.length > prefixToStrip.length
+ && value[prefixToStrip.length] !== '/')) {
+ return quote(value);
+ }
+ return quote("${" + data.installPrefixDir + "}" + value.slice(prefixToStrip.length));
+ }
+ return quoteAndPrefixifyHelper;
+ }
+
+ var installedOutputFilePath = ModUtils.artifactInstalledFilePath(
+ outputs["Exporter.cmake.configFile"][0]);
+ var installedOutputPathName = FileInfo.path(installedOutputFilePath);
+
+ var installRootPath = FileInfo.joinPaths(topLevelProduct.qbs.installRoot, topLevelProduct.qbs.installPrefix);
+ data.installPrefix = FileInfo.relativePath(installedOutputPathName, installRootPath);
+
+ var libArtifacts;
+ var libImportArtifacts;
+ var isProduct = !topLevelProduct.present;
+ var considerFramework = !isProduct || (topLevelProduct.type
+ && topLevelProduct.type.includes("bundle.content"))
+ && topLevelProduct.bundle
+ && topLevelProduct.bundle.isBundle
+ && topLevelProduct.qbs.targetOS.includes("darwin");
+ var considerDynamicLibs = !isProduct || (topLevelProduct.type
+ && topLevelProduct.type.includes("dynamiclibrary"));
+ var considerStaticLibs = !isProduct || (topLevelProduct.type
+ && topLevelProduct.type.includes("staticlibrary"));
+ if (considerFramework) {
+ libArtifacts = topLevelProduct.artifacts["bundle.symlink.executable"];
+ if (considerDynamicLibs)
+ data.type = "SHARED";
+ else if (considerStaticLibs)
+ data.type = "STATIC";
+ else
+ data.type = "INTERFACE";
+ } else if (considerDynamicLibs) {
+ libArtifacts = topLevelProduct.artifacts.dynamiclibrary;
+ libImportArtifacts = topLevelProduct.artifacts.dynamiclibrary_import;
+ data.type = "SHARED";
+ } else if (considerStaticLibs) {
+ libArtifacts = topLevelProduct.artifacts.staticlibrary;
+ data.type = "STATIC";
+ } else {
+ data.type = "INTERFACE";
+ }
+
+ for (var i = 0; i < (libArtifacts || []).length; ++i) {
+ var libArtifact = libArtifacts[i];
+ var libImportArtifact = (libImportArtifacts || [])[i];
+ if (libArtifact.qbs.install) {
+ var installPath = ModUtils.artifactInstalledFilePath(libArtifact);
+ data.importedLocation = quoteAndPrefixify("installRoot")(installPath);
+ data.soName = topLevelProduct.targetName;
+ if (libImportArtifact && libImportArtifact.qbs.install) {
+ installPath = ModUtils.artifactInstalledFilePath(libImportArtifact);
+ data.importedImplibLocation = quoteAndPrefixify("installRoot")(installPath);
+ }
+ break;
+ }
+ }
+ var cpp = topLevelProduct.exports.cpp;
+ if (cpp) {
+ data.libraryPaths = (cpp.libraryPaths || []).map(quoteAndPrefixify("cpp.libraryPaths"));
+
+ data.linkLibraries = [];
+ data.linkLibraries = data.linkLibraries.concat(cpp.dynamicLibraries || []);
+ data.linkLibraries = data.linkLibraries.concat(cpp.staticLibraries || []);
+ data.linkLibraries = data.linkLibraries.map(quoteAndPrefixify("cpp.dynamicLibraries"));
+
+ data.linkOptions = [];
+ data.linkOptions = data.linkOptions.concat(cpp.driverLinkerFlags || []);
+ if ((cpp.linkerFlags || []).length > 0) {
+ data.linkOptions =
+ data.linkOptions.concat("LINKER:" + (cpp.linkerFlags || []).join(","));
+ }
+ data.linkOptions = data.linkOptions.map(quote);
+
+ data.includeDirectories =
+ (cpp.includePaths || []).map(quoteAndPrefixify("cpp.includePaths"));
+ data.compileDefinitions = (cpp.defines || []).map(quote);
+
+ data.compileOptions = [];
+ data.compileOptions = data.compileOptions.concat(cpp.commonCompilerFlags || []);
+ data.compileOptions = data.compileOptions.concat(cpp.driverFlags || []);
+ data.compileOptions = data.compileOptions.concat(cpp.cxxFlags || []);
+ data.compileOptions = data.compileOptions.concat(cpp.cFlags || []);
+ data.compileOptions = data.compileOptions.map(quote);
+ }
+
+ function gatherDeps(dep) {
+ if (dep.name === "Exporter.cmake")
+ return;
+ var depHasExporter = dep.Exporter && dep.Exporter.cmake;
+ if (!depHasExporter)
+ return;
+ data.packages.push(dep.Exporter.cmake.packageName);
+ }
+
+ var exportedDeps = topLevelProduct.exports ? topLevelProduct.exports.dependencies : [];
+ exportedDeps.forEach(gatherDeps);
+
+ return data;
+}
+
+function writeConfigFile(project, product, outputs)
+{
+ var autoDetectedData = collectAutodetectedData(project, product, outputs);
+ var packageName = autoDetectedData.packageName;
+
+ function writeCommand(command, lines)
+ {
+ if ((lines || []).length === 0)
+ return;
+ cmakeConfigFile.writeLine(command + "(" + packageName + " INTERFACE");
+ for (i = 0; i < lines.length; i++) {
+ cmakeConfigFile.writeLine(" " + lines[i]);
+ }
+ cmakeConfigFile.writeLine(")");
+ }
+
+ var cmakeConfigFile = new TextFile(outputs["Exporter.cmake.configFile"][0].filePath,
+ TextFile.WriteOnly);
+ cmakeConfigFile.writeLine("# Generated by Qbs");
+
+ cmakeConfigFile.writeLine("cmake_minimum_required(VERSION 3.5)");
+
+ cmakeConfigFile.writeLine("if(TARGET " + packageName + ")");
+ cmakeConfigFile.writeLine(" return()");
+ cmakeConfigFile.writeLine("endif()");
+
+ cmakeConfigFile.writeLine("set(" + autoDetectedData.installPrefixDir +
+ " \"${CMAKE_CURRENT_LIST_DIR}/" +
+ autoDetectedData.installPrefix + "\")");
+
+ autoDetectedData.packages.forEach(function(packageName) {
+ cmakeConfigFile.writeLine("find_package(" + packageName + " REQUIRED SILENT)");
+ });
+ cmakeConfigFile.writeLine(
+ "add_library(" + packageName + " " + autoDetectedData.type + " IMPORTED)");
+ var configuration = (product.qbs.buildVariant) ?
+ product.qbs.buildVariant.toUpperCase() : "NONE";
+ cmakeConfigFile.writeLine("set_property(TARGET " + packageName +
+ " APPEND PROPERTY IMPORTED_CONFIGURATIONS " +
+ configuration + ")");
+
+ cmakeConfigFile.writeLine("set_target_properties(" + packageName + " PROPERTIES");
+ cmakeConfigFile.writeLine(" IMPORTED_LINK_INTERFACE_LANGUAGES_" + configuration +
+ " CXX");
+ if (autoDetectedData.type !== "INTERFACE") {
+ cmakeConfigFile.writeLine(" IMPORTED_LOCATION_" + configuration + " " +
+ autoDetectedData.importedLocation);
+ }
+ if (autoDetectedData.importedImplibLocation) {
+ cmakeConfigFile.writeLine(" IMPORTED_IMPLIB_" + configuration + " " +
+ autoDetectedData.importedImplibLocation);
+ }
+ cmakeConfigFile.writeLine(")");
+
+ writeCommand("target_link_directories", autoDetectedData.libraryPaths);
+ writeCommand("target_link_libraries",
+ autoDetectedData.linkLibraries.concat(autoDetectedData.packages));
+ writeCommand("target_link_options", autoDetectedData.linkOptions);
+ writeCommand("target_include_directories", autoDetectedData.includeDirectories);
+ writeCommand("target_compile_definitions", autoDetectedData.compileDefinitions);
+ writeCommand("target_compile_options", autoDetectedData.compileOptions);
+
+ cmakeConfigFile.close();
+}
+
+function writeVersionFile(product, outputs)
+{
+ var cmakeVersionFile = new TextFile(
+ outputs["Exporter.cmake.versionFile"][0].filePath, TextFile.WriteOnly);
+ cmakeVersionFile.writeLine("# Generated by Qbs");
+ cmakeVersionFile.writeLine("set(PACKAGE_VERSION \"" + product.version + "\")");
+ cmakeVersionFile.close();
+}
diff --git a/share/qbs/modules/Exporter/cmake/cmakeexporter.qbs b/share/qbs/modules/Exporter/cmake/cmakeexporter.qbs
new file mode 100644
index 000000000..a578e938b
--- /dev/null
+++ b/share/qbs/modules/Exporter/cmake/cmakeexporter.qbs
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Raphaël Cotty <raphael.cotty@gmail.com>
+** Copyright (C) 2024 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$
+**
+****************************************************************************/
+
+import qbs.File
+import qbs.FileInfo
+import qbs.ModUtils
+import qbs.TextFile
+
+import "cmakeexporter.js" as HelperFunctions
+
+Module {
+ property string configFileName: packageName + "Config.cmake"
+ property string versionFileName: packageName + "ConfigVersion.cmake"
+ property string packageName: product.targetName
+
+ additionalProductTypes: ["Exporter.cmake.package"]
+
+ Rule {
+ multiplex: true
+ requiresInputs: false
+
+ auxiliaryInputs: {
+ if (product.type.includes("staticlibrary"))
+ return ["staticlibrary"];
+ if (product.type.includes("dynamiclibrary"))
+ return ["dynamiclibrary"];
+ }
+
+ Artifact {
+ filePath: product.Exporter.cmake.configFileName
+ fileTags: ["Exporter.cmake.package", "Exporter.cmake.configFile"]
+ }
+ Artifact {
+ filePath: product.Exporter.cmake.versionFileName
+ fileTags: ["Exporter.cmake.package", "Exporter.cmake.versionFile"]
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "generate cmake package files";
+ cmd.sourceCode = function() {
+ HelperFunctions.writeConfigFile(project, product, outputs);
+ HelperFunctions.writeVersionFile(product, outputs);
+ }
+ return [cmd];
+ }
+ }
+}
diff --git a/share/qbs/modules/Exporter/exporter.js b/share/qbs/modules/Exporter/exporter.js
new file mode 100644
index 000000000..65a632ac8
--- /dev/null
+++ b/share/qbs/modules/Exporter/exporter.js
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of the 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.
+**
+****************************************************************************/
+
+function getPrefixToStrip(project, product, propName, value)
+{
+ function checkValuePrefix(forbiddenPrefix, prefixDescription)
+ {
+ if (value.startsWith(forbiddenPrefix)) {
+ throw "Value '" + value + "' for exported property '" + propName + "' in product '"
+ + product.name + "' points into " + prefixDescription + ".\n"
+ + "Did you forget to set the prefixMapping property in an Export item?";
+ }
+ }
+
+ // Catch user oversights: Paths that point into the project source or build directories
+ // make no sense in the module.
+ if (!value.startsWith(product.qbs.installRoot)) {
+ checkValuePrefix(project.buildDirectory, "project build directory");
+ checkValuePrefix(project.sourceDirectory, "project source directory");
+ }
+
+ // Adapt file paths pointing into the install dir, that is, make them relative to the
+ // module file for relocatability. We accept them with or without the install root.
+ // The latter form will typically be a result of applying the prefixMapping property,
+ // while the first one could be an untransformed path, for instance if the project
+ // file is written in such a way that include paths are picked up from the installed
+ // location rather than the source directory.
+ var result;
+ var fullInstallPrefix = FileInfo.joinPaths(product.qbs.installRoot, product.qbs.installPrefix);
+ if (fullInstallPrefix.length > 1 && value.startsWith(fullInstallPrefix)) {
+ result = fullInstallPrefix;
+ } else {
+ var installPrefix = FileInfo.joinPaths("/", product.qbs.installPrefix);
+ if (installPrefix.length > 1 && value.startsWith(installPrefix))
+ result = installPrefix;
+ }
+ return result;
+}
diff --git a/share/qbs/modules/Exporter/qbs/qbsexporter.js b/share/qbs/modules/Exporter/qbs/qbsexporter.js
index f09dd2d0c..145474a4b 100644
--- a/share/qbs/modules/Exporter/qbs/qbsexporter.js
+++ b/share/qbs/modules/Exporter/qbs/qbsexporter.js
@@ -30,6 +30,7 @@
var FileInfo = require("qbs.FileInfo");
var ModUtils = require("qbs.ModUtils");
+var ExporterHelpers = require("../exporter.js");
function tagListToString(tagList)
{
@@ -102,15 +103,6 @@ function writeTargetArtifactGroups(product, output, moduleFile)
}
}
-function checkValuePrefix(name, value, forbiddenPrefix, prefixDescription)
-{
- if (value.startsWith(forbiddenPrefix)) {
- throw "Value '" + value + "' for exported property '" + name + "' in product '"
- + product.name + "' points into " + prefixDescription + ".\n"
- + "Did you forget to set the prefixMapping property in an Export item?";
- }
-}
-
function stringifyValue(project, product, moduleInstallDir, prop, value)
{
if (value instanceof Array) {
@@ -128,29 +120,9 @@ function stringifyValue(project, product, moduleInstallDir, prop, value)
return value;
}
- // Catch user oversights: Paths that point into the project source or build directories
- // make no sense in the module.
- if (!value.startsWith(product.qbs.installRoot)) {
- checkValuePrefix(prop.name, value, project.buildDirectory, "project build directory");
- checkValuePrefix(prop.name, value, project.sourceDirectory, "project source directory");
- }
-
- // Adapt file paths pointing into the install dir, that is, make them relative to the
- // module file for relocatability. We accept them with or without the install root.
- // The latter form will typically be a result of applying the prefixMapping property,
- // while the first one could be an untransformed path, for instance if the project
- // file is written in such a way that include paths are picked up from the installed
- // location rather than the source directory.
- var valuePrefixToStrip;
- var fullInstallPrefix = FileInfo.joinPaths(product.qbs.installRoot, product.qbs.installPrefix);
- if (fullInstallPrefix.length > 1 && value.startsWith(fullInstallPrefix)) {
- valuePrefixToStrip = fullInstallPrefix;
- } else {
- var installPrefix = FileInfo.joinPaths("/", product.qbs.installPrefix);
- if (installPrefix.length > 1 && value.startsWith(installPrefix))
- valuePrefixToStrip = installPrefix;
- }
+ var valuePrefixToStrip = ExporterHelpers.getPrefixToStrip(project, product, prop.name, value);
if (valuePrefixToStrip) {
+ var fullInstallPrefix = FileInfo.joinPaths(product.qbs.installRoot, product.qbs.installPrefix);
var deployedModuleInstallDir = moduleInstallDir.slice(fullInstallPrefix.length);
return "FileInfo.cleanPath(FileInfo.joinPaths(path, FileInfo.relativePath("
+ JSON.stringify(deployedModuleInstallDir) + ", "
diff --git a/share/qbs/modules/Sanitizers/address/asan.qbs b/share/qbs/modules/Sanitizers/address/asan.qbs
index 9ae6f285e..9d8f5b97e 100644
--- a/share/qbs/modules/Sanitizers/address/asan.qbs
+++ b/share/qbs/modules/Sanitizers/address/asan.qbs
@@ -64,7 +64,8 @@ Module {
flags.push("-fsanitize-address-use-after-scope");
if (detectUseAfterReturn) {
if (qbs.toolchain.includes("llvm")) {
- if (Utilities.versionCompare(cpp.compilerVersion, "13") >= 0)
+ var minVersion = qbs.toolchain.contains("xcode") ? "14" : "13";
+ if (Utilities.versionCompare(cpp.compilerVersion, minVersion) >= 0)
flags.push("-fsanitize-address-use-after-return=" + detectUseAfterReturn);
} else if (detectUseAfterReturn === "never") {
flags.push("--param", "asan-use-after-return=0");
diff --git a/share/qbs/modules/bundle/BundleModule.qbs b/share/qbs/modules/bundle/BundleModule.qbs
index 0c2d7ba6a..f640fd55f 100644
--- a/share/qbs/modules/bundle/BundleModule.qbs
+++ b/share/qbs/modules/bundle/BundleModule.qbs
@@ -193,6 +193,11 @@ Module {
property string _productTypeIdentifier: Bundle.productTypeIdentifier(product.type)
property stringList _productTypeIdentifierChain: bundleSettingsProbe.productTypeIdentifierChain
+ readonly property path _developerPath: xcode.developerPath
+ readonly property path _platformInfoPlist: xcode.platformInfoPlist
+ readonly property path _sdkSettingsPlist: xcode.sdkSettingsPlist
+ readonly property path _toolchainInfoPlist: xcode.toolchainInfoPlist
+
property bool _useXcodeBuildSpecs: true // TODO: remove in 1.25
property var extraEnv: ({
@@ -310,10 +315,10 @@ Module {
// TODO: bundle module should know nothing about cpp module
cmd.buildEnv = product.moduleProperty("cpp", "buildEnv");
- cmd.developerPath = product.xcode.developerPath;
- cmd.platformInfoPlist = product.xcode.platformInfoPlist;
- cmd.sdkSettingsPlist = product.xcode.sdkSettingsPlist;
- cmd.toolchainInfoPlist = product.xcode.toolchainInfoPlist;
+ cmd.developerPath = product.bundle._developerPath;
+ cmd.platformInfoPlist = product.bundle._platformInfoPlist;
+ cmd.sdkSettingsPlist = product.bundle._sdkSettingsPlist;
+ cmd.toolchainInfoPlist = product.bundle._toolchainInfoPlist;
cmd.osBuildVersion = product.qbs.hostOSBuildVersion;
diff --git a/share/qbs/modules/bundle/bundle.js b/share/qbs/modules/bundle/bundle.js
index da23b7313..cf765f7be 100644
--- a/share/qbs/modules/bundle/bundle.js
+++ b/share/qbs/modules/bundle/bundle.js
@@ -152,6 +152,12 @@ function _assign(target, source) {
function macOSSpecsPaths(version, developerPath) {
var result = [];
+ if (Utilities.versionCompare(version, "15.3") >= 0) {
+ result.push(FileInfo.joinPaths(
+ developerPath, "..", "SharedFrameworks", "XCBuild.framework", "PlugIns",
+ "XCBBuildService.bundle", "Contents", "PlugIns", "XCBSpecifications.ideplugin",
+ "Contents", "Resources"));
+ }
if (Utilities.versionCompare(version, "14.3") >= 0) {
result.push(FileInfo.joinPaths(
developerPath, "Library", "Xcode", "Plug-ins", "XCBSpecifications.ideplugin",
@@ -313,7 +319,8 @@ var XcodeBuildSpecsReader = (function () {
};
XcodeBuildSpecsReader.prototype.expandedSetting = function (typeIdentifier, baseSettings,
settingName) {
- var obj = baseSettings || {};
+ var obj = {};
+ _assign(obj, baseSettings); // todo: copy recursively
obj = _assign(obj, this.settings(typeIdentifier, true));
if (obj) {
for (var x in this._additionalSettings) {
diff --git a/share/qbs/modules/capnproto/capnprotobase.qbs b/share/qbs/modules/capnproto/capnprotobase.qbs
index e84d39433..56d542770 100644
--- a/share/qbs/modules/capnproto/capnprotobase.qbs
+++ b/share/qbs/modules/capnproto/capnprotobase.qbs
@@ -40,7 +40,7 @@ Module {
property pathList importPaths: []
- readonly property string outputDir: product.buildDirectory + "/capnp"
+ property string outputDir: product.buildDirectory + "/capnp"
Probes.BinaryProbe {
id: compilerProbe
diff --git a/share/qbs/modules/capnproto/cpp/capnprotocpp.qbs b/share/qbs/modules/capnproto/cpp/capnprotocpp.qbs
index f33bc9a48..bccfca192 100644
--- a/share/qbs/modules/capnproto/cpp/capnprotocpp.qbs
+++ b/share/qbs/modules/capnproto/cpp/capnprotocpp.qbs
@@ -39,6 +39,7 @@ CapnProtoBase {
Depends { name: "capnp-rpc"; condition: useRpc }
pluginName: "capnpc-c++"
+ version: capnp.version
cpp.systemIncludePaths: outputDir
cpp.cxxLanguageVersion: "c++14"
diff --git a/share/qbs/modules/codesign/codesign.js b/share/qbs/modules/codesign/codesign.js
index 903d16f80..482225ea2 100644
--- a/share/qbs/modules/codesign/codesign.js
+++ b/share/qbs/modules/codesign/codesign.js
@@ -280,20 +280,21 @@ function prepareSign(project, product, inputs, outputs, input, output) {
return cmds;
var isBundle = "bundle.content" in outputs;
- var outputFilePath = isBundle
- ? FileInfo.joinPaths(product.destinationDirectory, product.bundle.bundleName)
- : outputs["codesign.signed_artifact"][0].filePath;
- var outputFileName = isBundle
- ? product.bundle.bundleName
- : outputs["codesign.signed_artifact"][0].fileName;
- var isProductBundle = product.bundle && product.bundle.isBundle;
- // If the product is a bundle, just sign the bundle
- // instead of signing the bundle and executable separately
+ var artifacts = [];
+ if (isBundle) {
+ artifacts = [{
+ filePath: FileInfo.joinPaths(product.destinationDirectory, product.bundle.bundleName),
+ fileName: product.bundle.bundleName
+ }];
+ } else {
+ artifacts = outputs["codesign.signed_artifact"];
+ }
+ var isProductBundle = product.bundle && product.bundle.isBundle;
var shouldSignArtifact = !isProductBundle || isBundle;
var enableCodeSigning = product.codesign.enableCodeSigning;
- if (enableCodeSigning && shouldSignArtifact) {
+ if (enableCodeSigning) {
var actualSigningIdentity = product.codesign._actualSigningIdentity;
if (!actualSigningIdentity) {
throw "No codesigning identities (i.e. certificate and private key pairs) matching “"
@@ -310,36 +311,53 @@ function prepareSign(project, product, inputs, outputs, input, output) {
}
}
- var args = ["--force", "--sign", actualSigningIdentity.SHA1];
-
- // If signingTimestamp is undefined or empty, do not specify the flag at all -
- // this uses the system-specific default behavior
- var signingTimestamp = product.codesign.signingTimestamp;
- if (signingTimestamp) {
- // If signingTimestamp is an empty string, specify the flag but do
- // not specify a value - this uses a default Apple-provided server
- var flag = "--timestamp";
- if (signingTimestamp)
- flag += "=" + signingTimestamp;
- args.push(flag);
+ // The codesign tool behaves weirdly. It can sign a bundle with a single artifact, but if
+ // say debug build variant is present, it starts complaining that it is not signed.
+ // We could always sign everything, but again, in case of a framework (but not in case of
+ // app or loadable bundle), codesign produces a warning that artifact is already signed.
+ // So, we skip signing the release artifact and only sign if other build variants present.
+ if (!shouldSignArtifact && artifacts.length == 1) {
+ artifacts = [];
}
+ for (var i = 0; i < artifacts.length; ++i) {
+ if (!shouldSignArtifact
+ && artifacts[i].qbs && artifacts[i].qbs.buildVariant === "release") {
+ continue;
+ }
+ var outputFilePath = artifacts[i].filePath;
+ var outputFileName = artifacts[i].fileName;
+
+ var args = ["--force", "--sign", actualSigningIdentity.SHA1];
+
+ // If signingTimestamp is undefined or empty, do not specify the flag at all -
+ // this uses the system-specific default behavior
+ var signingTimestamp = product.codesign.signingTimestamp;
+ if (signingTimestamp) {
+ // If signingTimestamp is an empty string, specify the flag but do
+ // not specify a value - this uses a default Apple-provided server
+ var flag = "--timestamp";
+ if (signingTimestamp)
+ flag += "=" + signingTimestamp;
+ args.push(flag);
+ }
- for (var j in inputs["codesign.xcent"]) {
- args.push("--entitlements", inputs["codesign.xcent"][j].filePath);
- break; // there should only be one
- }
+ for (var j in inputs["codesign.xcent"]) {
+ args.push("--entitlements", inputs["codesign.xcent"][j].filePath);
+ break; // there should only be one
+ }
- args = args.concat(product.codesign.codesignFlags || []);
+ args = args.concat(product.codesign.codesignFlags || []);
- args.push(outputFilePath + subpath);
- cmd = new Command(product.codesign.codesignPath, args);
- cmd.description = "codesign " + outputFileName
- + " (" + actualSigningIdentity.subjectInfo.CN + ")";
- cmd.outputFilePath = outputFilePath;
- cmd.stderrFilterFunction = function(stderr) {
- return stderr.replace(outputFilePath + ": replacing existing signature\n", "");
- };
- cmds.push(cmd);
+ args.push(outputFilePath + subpath);
+ cmd = new Command(product.codesign.codesignPath, args);
+ cmd.description = "codesign " + outputFileName
+ + " (" + actualSigningIdentity.subjectInfo.CN + ")";
+ cmd.outputFilePath = outputFilePath;
+ cmd.stderrFilterFunction = function(stderr) {
+ return stderr.replace(outputFilePath + ": replacing existing signature\n", "");
+ };
+ cmds.push(cmd);
+ }
}
if (isBundle) {
diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs
index 05ceccf5f..f84028940 100644
--- a/share/qbs/modules/cpp/GenericGCC.qbs
+++ b/share/qbs/modules/cpp/GenericGCC.qbs
@@ -170,7 +170,7 @@ CppModule {
property string linkerVariant
PropertyOptions {
name: "linkerVariant"
- allowedValues: ["bfd", "gold", "lld"]
+ allowedValues: ["bfd", "gold", "lld", "mold"]
description: "Allows to specify the linker variant. Maps to gcc's and clang's -fuse-ld "
+ "option."
}
@@ -252,7 +252,7 @@ CppModule {
property var buildEnv: {
var env = {};
if (qbs.toolchain.includes("mingw"))
- env.PATH = [toolchainInstallPath]; // For libwinpthread etc
+ env.PATH = toolchainInstallPath; // For libwinpthread etc
return env;
}
diff --git a/share/qbs/modules/cpp/cpp.js b/share/qbs/modules/cpp/cpp.js
index a13504013..9f907580a 100644
--- a/share/qbs/modules/cpp/cpp.js
+++ b/share/qbs/modules/cpp/cpp.js
@@ -327,7 +327,10 @@ function collectIncludePaths(input) {
var includePaths = input.cpp.includePaths;
if (includePaths)
allIncludePaths = allIncludePaths.uniqueConcat(includePaths);
- return allIncludePaths;
+ var builtIns = input.cpp.compilerIncludePaths;
+ return allIncludePaths.filter(function(p) {
+ return !builtIns.includes(p);
+ });
}
function collectSystemIncludePaths(input) {
@@ -338,7 +341,10 @@ function collectSystemIncludePaths(input) {
var distributionIncludePaths = input.cpp.distributionIncludePaths;
if (distributionIncludePaths)
allIncludePaths = allIncludePaths.uniqueConcat(distributionIncludePaths);
- return allIncludePaths;
+ var builtIns = input.cpp.compilerIncludePaths;
+ return allIncludePaths.filter(function(p) {
+ return !builtIns.includes(p);
+ });
}
function collectPreincludePaths(input) {
diff --git a/share/qbs/modules/cpp/gcc.js b/share/qbs/modules/cpp/gcc.js
index d9213e951..90f8fc933 100644
--- a/share/qbs/modules/cpp/gcc.js
+++ b/share/qbs/modules/cpp/gcc.js
@@ -78,6 +78,7 @@ function useCompilerDriverLinker(product, inputs) {
function collectLibraryDependencies(product, isDarwin) {
var publicDeps = {};
+ var privateDeps = {};
var objects = [];
var objectByFilePath = {};
var tagForLinkingAgainstSharedLib = product.cpp.imageFormat === "pe"
@@ -170,6 +171,8 @@ function collectLibraryDependencies(product, isDarwin) {
&& typeof dep.artifacts[tagForLinkingAgainstSharedLib] !== "undefined";
if (!isStaticLibrary && !isDynamicLibrary)
return;
+ if (isBelowIndirectDynamicLib && privateDeps[dep.name])
+ return;
var nextIsBelowIndirectDynamicLib = isBelowIndirectDynamicLib || isDynamicLibrary;
dep.dependencies.forEach(function(depdep) {
@@ -193,6 +196,7 @@ function collectLibraryDependencies(product, isDarwin) {
publicDeps[dep.name] = true;
} else {
addArtifactFilePaths(dep, tagForLinkingAgainstSharedLib, addPrivateFilePath);
+ privateDeps[dep.name] = true;
}
}
}
@@ -204,7 +208,6 @@ function collectLibraryDependencies(product, isDarwin) {
product.dependencies.forEach(traverseDirectDependency);
addExternalLibs(product);
- var seenRPathLinkDirs = {};
var result = { libraries: [], rpath_link: [] };
objects.forEach(
function (obj) {
@@ -215,10 +218,7 @@ function collectLibraryDependencies(product, isDarwin) {
framework: obj.framework });
} else {
var dirPath = FileInfo.path(obj.filePath);
- if (!seenRPathLinkDirs.hasOwnProperty(dirPath)) {
- seenRPathLinkDirs[dirPath] = true;
- result.rpath_link.push(dirPath);
- }
+ result.rpath_link.push(dirPath);
}
});
return result;
@@ -406,8 +406,8 @@ function linkerFlags(project, product, inputs, outputs, primaryOutput, linkerPat
// Flags for library search paths
var allLibraryPaths = Cpp.collectLibraryPaths(product);
- if (systemRunPaths.length > 0)
- allLibraryPaths = allLibraryPaths.filter(isNotSystemRunPath);
+ var builtIns = product.cpp.compilerLibraryPaths
+ allLibraryPaths = allLibraryPaths.filter(function(p) { return !builtIns.includes(p); });
args = args.concat(allLibraryPaths.map(function(path) { return product.cpp.libraryPathFlag + path }));
escapableLinkerFlags = escapableLinkerFlags.concat(Cpp.collectLinkerScriptPathsArguments(product, inputs));
@@ -477,7 +477,10 @@ function linkerFlags(project, product, inputs, outputs, primaryOutput, linkerPat
if (isDarwin && symbolLinkMode) {
if (!["lazy", "reexport", "upward", "weak"].includes(symbolLinkMode))
throw new Error("unknown value '" + symbolLinkMode + "' for cpp.symbolLinkMode");
+ }
+ var supportsLazyMode = Utilities.versionCompare(product.cpp.compilerVersion, "15.0.0") < 0;
+ if (isDarwin && symbolLinkMode && (symbolLinkMode !== "lazy" || supportsLazyMode)) {
if (FileInfo.isAbsolutePath(lib) || lib.startsWith('@'))
escapableLinkerFlags.push("-" + symbolLinkMode + "_library", lib);
else if (dep.framework)
diff --git a/share/qbs/modules/cpp/msvc.js b/share/qbs/modules/cpp/msvc.js
index 6127279fb..1700c21b3 100644
--- a/share/qbs/modules/cpp/msvc.js
+++ b/share/qbs/modules/cpp/msvc.js
@@ -249,14 +249,17 @@ function prepareCompiler(project, product, inputs, outputs, input, output, expli
return input.cpp.includeFlag + FileInfo.toWindowsSeparators(path);
}));
- var includeFlag = input.cpp.includeFlag;
- if (supportsExternalIncludesOption(input)) {
- args.push("/experimental:external");
- var enforcesSlashW =
- Utilities.versionCompare(input.cpp.compilerVersion, "19.29.30037") >= 0
- if (enforcesSlashW)
- args.push("/external:W0")
- includeFlag = input.cpp.systemIncludeFlag;
+ var includeFlag = input.qbs.toolchain.includes("clang-cl")
+ ? input.cpp.systemIncludeFlag : input.cpp.includeFlag;
+ if (!input.qbs.toolchain.includes("clang-cl")) {
+ if (supportsExternalIncludesOption(input)) {
+ args.push("/experimental:external");
+ var enforcesSlashW =
+ Utilities.versionCompare(input.cpp.compilerVersion, "19.29.30037") >= 0
+ if (enforcesSlashW)
+ args.push("/external:W0")
+ includeFlag = input.cpp.systemIncludeFlag;
+ }
}
var systemIncludePaths = Cpp.collectSystemIncludePaths(input);
args = args.concat([].uniqueConcat(systemIncludePaths).map(function(path) {
diff --git a/share/qbs/modules/cpp/windows-clang-cl.qbs b/share/qbs/modules/cpp/windows-clang-cl.qbs
index 32abb572a..9aa9d7395 100644
--- a/share/qbs/modules/cpp/windows-clang-cl.qbs
+++ b/share/qbs/modules/cpp/windows-clang-cl.qbs
@@ -85,6 +85,8 @@ MsvcBaseModule {
linkerName: "lld-link.exe"
linkerPath: FileInfo.joinPaths(toolchainInstallPath, linkerName)
+ systemIncludeFlag: "/imsvc"
+
validateFunc: {
var baseFunc = base;
return function() {
diff --git a/share/qbs/modules/cpp/windows-msvc-base.qbs b/share/qbs/modules/cpp/windows-msvc-base.qbs
index 1a2c338db..9613800e5 100644
--- a/share/qbs/modules/cpp/windows-msvc-base.qbs
+++ b/share/qbs/modules/cpp/windows-msvc-base.qbs
@@ -320,12 +320,11 @@ CppModule {
outputFileTags: Cpp.assemblerOutputTags(false)
outputArtifacts: Cpp.assemblerOutputArtifacts(input)
prepare: {
- var args = ["/nologo", "/c",
- "/Fo" + FileInfo.toWindowsSeparators(output.filePath),
- FileInfo.toWindowsSeparators(input.filePath)];
+ var args = ["/nologo", "/c", "/Fo" + FileInfo.toWindowsSeparators(output.filePath)];
if (product.cpp.debugInformation)
args.push("/Zi");
args = args.concat(Cpp.collectMiscAssemblerArguments(input, "asm"));
+ args.push(FileInfo.toWindowsSeparators(input.filePath));
var cmd = new Command(product.cpp.assemblerPath, args);
cmd.description = "assembling " + input.fileName;
cmd.jobPool = "assembler";
diff --git a/share/qbs/modules/dmg/DMGModule.qbs b/share/qbs/modules/dmg/DMGModule.qbs
index 59161b1db..27a945033 100644
--- a/share/qbs/modules/dmg/DMGModule.qbs
+++ b/share/qbs/modules/dmg/DMGModule.qbs
@@ -34,6 +34,7 @@ import qbs.FileInfo
import qbs.Host
import qbs.ModUtils
import qbs.Process
+import qbs.Probes
import qbs.TextFile
import "dmg.js" as Dmg
@@ -73,6 +74,12 @@ Module {
property string sourceBase
+ Probes.BinaryProbe {
+ id: pythonProbe
+ names: ["python3"]
+ }
+ property string pythonExePath: pythonProbe.found ? pythonProbe.filePath : "python3"
+
readonly property string pythonPath: File.canonicalFilePath(FileInfo.joinPaths(path,
"..", "..",
"python"))
diff --git a/share/qbs/modules/dmg/dmg.js b/share/qbs/modules/dmg/dmg.js
index 7d50d95f3..06fe7b906 100644
--- a/share/qbs/modules/dmg/dmg.js
+++ b/share/qbs/modules/dmg/dmg.js
@@ -135,7 +135,7 @@ function dmgbuildSettings(product, inputs) {
}
});
- return {
+ var result = {
"title": product.dmg.volumeName,
"icon": !product.dmg.badgeVolumeIcon ? volumeIcon : undefined,
"badge-icon": product.dmg.badgeVolumeIcon ? volumeIcon : undefined,
@@ -154,13 +154,16 @@ function dmgbuildSettings(product, inputs) {
},
"format": product.dmg.format,
"compression-level": product.dmg.compressionLevel,
- "license": {
+ "contents": contentsArray
+ };
+ if (licenseFileObjects.length >= 0) {
+ result["license"] = {
"default-language": product.dmg.defaultLicenseLocale,
"licenses": reduceLicensesForKey(licenseFileObjects, "licenses"),
"buttons": reduceLicensesForKey(licenseFileObjects, "buttons")
- },
- "contents": contentsArray
- };
+ };
+ }
+ return result;
}
function prepareLicense(project, product, inputs, outputs, input, output) {
@@ -199,8 +202,10 @@ function prepareDmg(project, product, inputs, outputs, input, output) {
cmds.push(cmd);
// Create the actual DMG via dmgbuild
- cmd = new Command(FileInfo.joinPaths(product.qbs.libexecPath, "dmgbuild"),
- [product.dmg.volumeName,
+ cmd = new Command(product.dmg.pythonExePath,
+ ["-m",
+ "dmgbuild",
+ product.dmg.volumeName,
output.filePath,
"--no-hidpi", // qbs handles this by itself
"--settings", settingsJsonFilePath]);
diff --git a/share/qbs/modules/java/utils.js b/share/qbs/modules/java/utils.js
index 9ee5932dc..a59b8d41d 100644
--- a/share/qbs/modules/java/utils.js
+++ b/share/qbs/modules/java/utils.js
@@ -99,16 +99,44 @@ function findJdkPath(hostOS, arch, environmentPaths, searchPaths) {
try {
// We filter by architecture here so that we'll get a compatible JVM for JNI use.
var args = [];
+ var canonicalArch;
if (arch) {
// Hardcoding apple/macosx/macho here is fine because we know we're on macOS
- args.push("--arch",
- Utilities.canonicalTargetArchitecture(arch, undefined,
- "apple", "macosx", "macho"));
+ canonicalArch = Utilities.canonicalTargetArchitecture(arch, undefined, "apple",
+ "macosx", "macho");
+ args.push("--arch", canonicalArch);
}
// --failfast doesn't print the default JVM if nothing matches the filter(s).
var status = p.exec("/usr/libexec/java_home", args.concat(["--failfast"]));
- return status === 0 ? p.readStdOut().trim() : undefined;
+ if (status === 0)
+ return p.readStdOut().trim();
+
+ // It has been obvserved that java_home fails for any architecture that is passed,
+ // so try without the filter and look up the JDK architecture manually.
+ if (!canonicalArch)
+ return undefined;
+
+ if (p.exec("/usr/libexec/java_home", ["--failfast"]) !== 0)
+ return undefined;
+ var jdkPath = p.readStdOut().trim();
+ var releaseFile = new TextFile(jdkPath + "/release", TextFile.ReadOnly);
+ var line;
+ while ((line = releaseFile.readLine())) {
+ if (!line.startsWith("OS_ARCH="))
+ continue;
+ var firstQuote = line.indexOf('"');
+ if (firstQuote === -1)
+ break;
+ var secondQuote = line.indexOf('"', firstQuote + 1);
+ if (secondQuote === -1)
+ break;
+ var archFromFile = line.substring(firstQuote + 1, secondQuote);
+ if (archFromFile !== canonicalArch)
+ break;
+ return jdkPath;
+ }
+ return undefined;
} finally {
p.close();
}
diff --git a/share/qbs/modules/protobuf/cpp/protobufcpp.qbs b/share/qbs/modules/protobuf/cpp/protobufcpp.qbs
index ae218a0c2..b5dab2372 100644
--- a/share/qbs/modules/protobuf/cpp/protobufcpp.qbs
+++ b/share/qbs/modules/protobuf/cpp/protobufcpp.qbs
@@ -6,40 +6,26 @@ import "../protobufbase.qbs" as ProtobufBase
import "../protobuf.js" as HelperFunctions
ProtobufBase {
- property string includePath: includeProbe.found ? includeProbe.path : undefined
- property string libraryPath: libraryProbe.found ? libraryProbe.path : undefined
-
property bool useGrpc: false
property bool _linkLibraries: true
- property stringList _extraGrpcLibs: []
readonly property bool _hasModules: protobuflib.present && (!useGrpc || grpcpp.present)
- property string grpcIncludePath: grpcIncludeProbe.found ? grpcIncludeProbe.path : undefined
- property string grpcLibraryPath: grpcLibraryProbe.found ? grpcLibraryProbe.path : undefined
+ property string _cxxLanguageVersion: "c++17"
- readonly property string _libraryName: {
- var libraryName;
- if (libraryProbe.found) {
- libraryName = FileInfo.baseName(libraryProbe.fileName);
- if (libraryName.startsWith("lib"))
- libraryName = libraryName.substring(3);
- }
- return libraryName;
- }
+ cpp.includePaths: outputDir
Depends { name: "cpp" }
Depends {
name: "protobuflib";
condition: _linkLibraries;
- required: false;
- enableFallback: false
+ required: false
}
Depends {
- name: "grpcpp";
+ name: "grpc++";
+ id: grpcpp
condition: _linkLibraries && useGrpc;
- required: false;
- enableFallback: false
+ required: false
}
property path grpcPluginPath: grpcPluginProbe.filePath
@@ -50,43 +36,7 @@ ProtobufBase {
names: "grpc_cpp_plugin"
}
- cpp.libraryPaths: {
- if (!_linkLibraries || _hasModules)
- return [];
-
- var result = [];
- if (libraryProbe.found)
- result.push(libraryProbe.path);
- if (useGrpc && grpcLibraryProbe.found)
- result.push(grpcLibraryPath);
- return result;
- }
- cpp.dynamicLibraries: {
- if (!_linkLibraries || _hasModules)
- return [];
-
- var result = [];
- if (_libraryName)
- result.push(_libraryName)
- if (qbs.targetOS.includes("unix"))
- result.push("pthread");
- if (useGrpc) {
- result = result.concat(_extraGrpcLibs);
- result.push("grpc++");
- }
- return result;
- }
- cpp.includePaths: {
- if (!_linkLibraries || _hasModules)
- return [outputDir];
-
- var result = [outputDir];
- if (includeProbe.found)
- result.push(includePath);
- if (useGrpc && grpcIncludeProbe.found)
- result.push(grpcIncludePath);
- return result;
- }
+ cpp.cxxLanguageVersion: _cxxLanguageVersion
Rule {
inputs: ["protobuf.input", "protobuf.grpc"]
@@ -120,56 +70,16 @@ ProtobufBase {
}
}
- Probes.IncludeProbe {
- id: includeProbe
- names: "google/protobuf/message.h"
- platformSearchPaths: includePath ? [] : base
- searchPaths: includePath ? [includePath] : []
- }
-
- Probes.LibraryProbe {
- id: libraryProbe
- names: [
- "protobuf",
- "protobufd",
- ]
- platformSearchPaths: libraryPath ? [] : base
- searchPaths: libraryPath ? [libraryPath] : []
- }
-
- Probes.IncludeProbe {
- id: grpcIncludeProbe
- pathSuffixes: "grpc++"
- names: "grpc++.h"
- platformSearchPaths: grpcIncludePath ? [] : base
- searchPaths: grpcIncludePath ? [grpcIncludePath] : []
- }
-
- Probes.LibraryProbe {
- id: grpcLibraryProbe
- names: "grpc++"
- platformSearchPaths: grpcLibraryPath ? [] : base
- searchPaths: grpcLibraryPath ? [grpcLibraryPath] : []
- }
-
validate: {
HelperFunctions.validateCompiler(compilerName, compilerPath);
- if (_hasModules)
- return;
-
- if (_linkLibraries && !includeProbe.found)
- throw "Can't find cpp protobuf include files. Please set the includePath property.";
- if (_linkLibraries && !libraryProbe.found)
- throw "Can't find cpp protobuf library. Please set the libraryPath property.";
+ if (_linkLibraries && ! _hasModules) {
+ throw "Can't find cpp protobuf runtime. Make sure .pc files are present";
+ }
if (useGrpc) {
if (!File.exists(grpcPluginPath))
throw "Can't find grpc_cpp_plugin plugin. Please set the grpcPluginPath property.";
- if (_linkLibraries && !grpcIncludeProbe.found)
- throw "Can't find grpc++ include files. Please set the grpcIncludePath property.";
- if (_linkLibraries && !grpcLibraryProbe.found)
- throw "Can't find grpc++ library. Please set the grpcLibraryPath property.";
}
}
}
diff --git a/share/qbs/modules/protobuf/nanopb/nanopb.qbs b/share/qbs/modules/protobuf/nanopb/nanopb.qbs
index 898e230b6..ada7c20bb 100644
--- a/share/qbs/modules/protobuf/nanopb/nanopb.qbs
+++ b/share/qbs/modules/protobuf/nanopb/nanopb.qbs
@@ -41,6 +41,7 @@ ProtobufBase {
result.push(includePath);
return result;
}
+ cpp.cxxLanguageVersion: qbs.targetOS.contains("darwin") ? ["c++17"] : ["c++11"]
Rule {
inputs: ["protobuf.input"]
diff --git a/share/qbs/modules/xcode/xcode.js b/share/qbs/modules/xcode/xcode.js
index 6bd37ad10..726974579 100644
--- a/share/qbs/modules/xcode/xcode.js
+++ b/share/qbs/modules/xcode/xcode.js
@@ -112,7 +112,7 @@ function sdkInfoList(sdksPath) {
if (!sdks[i].match(/[0-9]+/))
continue;
- if (sdks[i].startsWith("DriverKit"))
+ if (sdks[i].startsWith("DriverKit") || sdks[i].startsWith("AssetRuntime"))
continue;
var settingsPlist = FileInfo.joinPaths(sdksPath, sdks[i], "SDKSettings.plist");
@@ -205,9 +205,16 @@ function boolFromSdkOrPlatform(varName, sdkProps, platformProps, defaultValue) {
function archsSpecsPath(version, targetOS, platformType, platformPath, devicePlatformPath,
developerPath) {
if (Utilities.versionCompare(version, "13.3") >= 0) {
- var pluginsDir = Utilities.versionCompare(version, "14.3") >= 0
- ? FileInfo.joinPaths(developerPath, "Library", "Xcode", "Plug-ins")
- : FileInfo.joinPaths(developerPath, "..", "PlugIns");
+ var pluginsDir;
+ if (Utilities.versionCompare(version, "15.3") >= 0) {
+ pluginsDir = FileInfo.joinPaths(developerPath, "..",
+ "SharedFrameworks", "XCBuild.framework", "PlugIns", "XCBBuildService.bundle",
+ "Contents", "PlugIns");
+ } else if (Utilities.versionCompare(version, "14.3") >= 0) {
+ pluginsDir = FileInfo.joinPaths(developerPath, "Library", "Xcode", "Plug-ins");
+ } else {
+ pluginsDir = FileInfo.joinPaths(developerPath, "..", "PlugIns");
+ }
var baseDir = FileInfo.joinPaths(pluginsDir,
"XCBSpecifications.ideplugin", "Contents", "Resources");
diff --git a/share/qbs/modules/xcode/xcode.qbs b/share/qbs/modules/xcode/xcode.qbs
index 941b8d395..c052da44c 100644
--- a/share/qbs/modules/xcode/xcode.qbs
+++ b/share/qbs/modules/xcode/xcode.qbs
@@ -134,6 +134,7 @@ Module {
readonly property var _sdkSettings: {
if (_availableSdks) {
+ // see 'sdk' property doc to understand why this loop is needed
for (var i in _availableSdks) {
if (_availableSdks[i]["Version"] === sdk)
return _availableSdks[i];
@@ -141,7 +142,7 @@ Module {
return _availableSdks[i];
}
- // Latest SDK available for the platform
+ // Latest SDK available for the platform (default case)
if (DarwinTools.applePlatformName(qbs.targetOS, platformType) === sdk)
return _latestSdk;
}
diff --git a/share/share.qbs b/share/share.qbs
index 97af30396..75e1e83ba 100644
--- a/share/share.qbs
+++ b/share/share.qbs
@@ -18,22 +18,13 @@ Product {
}
Group {
- name: "Python executables"
- files: ["../src/3rdparty/python/bin/dmgbuild"]
- fileTags: ["qbs resources"]
- qbs.install: true
- qbs.installDir: qbsbuildconfig.libexecInstallDir
- qbs.installSourceBase: "../src/3rdparty/python/bin"
- }
-
- Group {
name: "Python packages"
prefix: "../src/3rdparty/python/**/"
files: ["*.py"]
fileTags: ["qbs resources"]
qbs.install: true
qbs.installDir: qbsbuildconfig.resourcesInstallDir + "/share/qbs/python"
- qbs.installSourceBase: "../src/3rdparty/python/lib/python2.7/site-packages"
+ qbs.installSourceBase: "../src/3rdparty/python/lib/python3.9/site-packages"
}
Group {
diff --git a/src/3rdparty/python/bin/dmgbuild b/src/3rdparty/python/bin/dmgbuild
deleted file mode 100755
index 74fc367c8..000000000
--- a/src/3rdparty/python/bin/dmgbuild
+++ /dev/null
@@ -1,36 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from __future__ import unicode_literals
-from __future__ import print_function
-
-import dmgbuild
-import sys
-import argparse
-
-parser = argparse.ArgumentParser(description='Construct a disk image file.')
-parser.add_argument('volume_name', metavar='volume-name',
- help='The name to give to the volume (this will appear in the title bar when the user mounts the disk image).')
-parser.add_argument('filename', metavar='output.dmg',
- help='The filename of the disk image to create.')
-parser.add_argument('-s', '--settings',
- help='The path of the settings file.')
-parser.add_argument('-D', dest='defines', action='append', default=[],
- help='Define a value for the settings file (e.g. -Dfoo=bar).')
-parser.add_argument('--no-hidpi', dest='lookForHiDPI', action='store_false', default=True,
- help='Do not search for HiDPI versions of the background image (if specified)')
-
-
-args = parser.parse_args()
-
-defines = {}
-for d in args.defines:
- k,v = d.split('=', 1)
- k = k.strip()
- v = v.strip()
- if (v.startswith("'") and v.endswith("'")) \
- or (v.startswith('"') and v.endswith('"')):
- v = v[1:-1]
- defines[k] = v
-
-dmgbuild.build_dmg(args.filename, args.volume_name, args.settings, defines=defines, lookForHiDPI=args.lookForHiDPI)
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/biplist/LICENSE b/src/3rdparty/python/lib/python2.7/site-packages/biplist/LICENSE
deleted file mode 100644
index 1c7ba6cc1..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/biplist/LICENSE
+++ /dev/null
@@ -1,25 +0,0 @@
-Copyright (c) 2010, Andrew Wooster
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of biplist nor the names of its contributors may be
- used to endorse or promote products derived from this software without
- specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/biplist/__init__.py b/src/3rdparty/python/lib/python2.7/site-packages/biplist/__init__.py
deleted file mode 100644
index f9d5836dd..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/biplist/__init__.py
+++ /dev/null
@@ -1,977 +0,0 @@
-"""biplist -- a library for reading and writing binary property list files.
-
-Binary Property List (plist) files provide a faster and smaller serialization
-format for property lists on OS X. This is a library for generating binary
-plists which can be read by OS X, iOS, or other clients.
-
-The API models the plistlib API, and will call through to plistlib when
-XML serialization or deserialization is required.
-
-To generate plists with UID values, wrap the values with the Uid object. The
-value must be an int.
-
-To generate plists with NSData/CFData values, wrap the values with the
-Data object. The value must be a string.
-
-Date values can only be datetime.datetime objects.
-
-The exceptions InvalidPlistException and NotBinaryPlistException may be
-thrown to indicate that the data cannot be serialized or deserialized as
-a binary plist.
-
-Plist generation example:
-
- from biplist import *
- from datetime import datetime
- plist = {'aKey':'aValue',
- '0':1.322,
- 'now':datetime.now(),
- 'list':[1,2,3],
- 'tuple':('a','b','c')
- }
- try:
- writePlist(plist, "example.plist")
- except (InvalidPlistException, NotBinaryPlistException), e:
- print "Something bad happened:", e
-
-Plist parsing example:
-
- from biplist import *
- try:
- plist = readPlist("example.plist")
- print plist
- except (InvalidPlistException, NotBinaryPlistException), e:
- print "Not a plist:", e
-"""
-
-from collections import namedtuple
-import datetime
-import io
-import math
-import plistlib
-from struct import pack, unpack, unpack_from
-from struct import error as struct_error
-import sys
-import time
-
-try:
- unicode
- unicodeEmpty = r''
-except NameError:
- unicode = str
- unicodeEmpty = ''
-try:
- long
-except NameError:
- long = int
-try:
- {}.iteritems
- iteritems = lambda x: x.iteritems()
-except AttributeError:
- iteritems = lambda x: x.items()
-
-__all__ = [
- 'Uid', 'Data', 'readPlist', 'writePlist', 'readPlistFromString',
- 'writePlistToString', 'InvalidPlistException', 'NotBinaryPlistException'
-]
-
-# Apple uses Jan 1, 2001 as a base for all plist date/times.
-apple_reference_date = datetime.datetime.utcfromtimestamp(978307200)
-
-class Uid(object):
- """Wrapper around integers for representing UID values. This
- is used in keyed archiving."""
- integer = 0
- def __init__(self, integer):
- self.integer = integer
-
- def __repr__(self):
- return "Uid(%d)" % self.integer
-
- def __eq__(self, other):
- if isinstance(self, Uid) and isinstance(other, Uid):
- return self.integer == other.integer
- return False
-
- def __cmp__(self, other):
- return self.integer - other.integer
-
- def __lt__(self, other):
- return self.integer < other.integer
-
- def __hash__(self):
- return self.integer
-
- def __int__(self):
- return int(self.integer)
-
-class Data(bytes):
- """Wrapper around bytes to distinguish Data values."""
-
-class InvalidPlistException(Exception):
- """Raised when the plist is incorrectly formatted."""
-
-class NotBinaryPlistException(Exception):
- """Raised when a binary plist was expected but not encountered."""
-
-def readPlist(pathOrFile):
- """Raises NotBinaryPlistException, InvalidPlistException"""
- didOpen = False
- result = None
- if isinstance(pathOrFile, (bytes, unicode)):
- pathOrFile = open(pathOrFile, 'rb')
- didOpen = True
- try:
- reader = PlistReader(pathOrFile)
- result = reader.parse()
- except NotBinaryPlistException as e:
- try:
- pathOrFile.seek(0)
- result = None
- if hasattr(plistlib, 'loads'):
- contents = None
- if isinstance(pathOrFile, (bytes, unicode)):
- with open(pathOrFile, 'rb') as f:
- contents = f.read()
- else:
- contents = pathOrFile.read()
- result = plistlib.loads(contents)
- else:
- result = plistlib.readPlist(pathOrFile)
- result = wrapDataObject(result, for_binary=True)
- except Exception as e:
- raise InvalidPlistException(e)
- finally:
- if didOpen:
- pathOrFile.close()
- return result
-
-def wrapDataObject(o, for_binary=False):
- if isinstance(o, Data) and not for_binary:
- v = sys.version_info
- if not (v[0] >= 3 and v[1] >= 4):
- o = plistlib.Data(o)
- elif isinstance(o, (bytes, plistlib.Data)) and for_binary:
- if hasattr(o, 'data'):
- o = Data(o.data)
- elif isinstance(o, tuple):
- o = wrapDataObject(list(o), for_binary)
- o = tuple(o)
- elif isinstance(o, list):
- for i in range(len(o)):
- o[i] = wrapDataObject(o[i], for_binary)
- elif isinstance(o, dict):
- for k in o:
- o[k] = wrapDataObject(o[k], for_binary)
- return o
-
-def writePlist(rootObject, pathOrFile, binary=True):
- if not binary:
- rootObject = wrapDataObject(rootObject, binary)
- if hasattr(plistlib, "dump"):
- if isinstance(pathOrFile, (bytes, unicode)):
- with open(pathOrFile, 'wb') as f:
- return plistlib.dump(rootObject, f)
- else:
- return plistlib.dump(rootObject, pathOrFile)
- else:
- return plistlib.writePlist(rootObject, pathOrFile)
- else:
- didOpen = False
- if isinstance(pathOrFile, (bytes, unicode)):
- pathOrFile = open(pathOrFile, 'wb')
- didOpen = True
- writer = PlistWriter(pathOrFile)
- result = writer.writeRoot(rootObject)
- if didOpen:
- pathOrFile.close()
- return result
-
-def readPlistFromString(data):
- return readPlist(io.BytesIO(data))
-
-def writePlistToString(rootObject, binary=True):
- if not binary:
- rootObject = wrapDataObject(rootObject, binary)
- if hasattr(plistlib, "dumps"):
- return plistlib.dumps(rootObject)
- elif hasattr(plistlib, "writePlistToBytes"):
- return plistlib.writePlistToBytes(rootObject)
- else:
- return plistlib.writePlistToString(rootObject)
- else:
- ioObject = io.BytesIO()
- writer = PlistWriter(ioObject)
- writer.writeRoot(rootObject)
- return ioObject.getvalue()
-
-def is_stream_binary_plist(stream):
- stream.seek(0)
- header = stream.read(7)
- if header == b'bplist0':
- return True
- else:
- return False
-
-PlistTrailer = namedtuple('PlistTrailer', 'offsetSize, objectRefSize, offsetCount, topLevelObjectNumber, offsetTableOffset')
-PlistByteCounts = namedtuple('PlistByteCounts', 'nullBytes, boolBytes, intBytes, realBytes, dateBytes, dataBytes, stringBytes, uidBytes, arrayBytes, setBytes, dictBytes')
-
-class PlistReader(object):
- file = None
- contents = ''
- offsets = None
- trailer = None
- currentOffset = 0
- # Used to detect recursive object references.
- offsetsStack = []
-
- def __init__(self, fileOrStream):
- """Raises NotBinaryPlistException."""
- self.reset()
- self.file = fileOrStream
-
- def parse(self):
- return self.readRoot()
-
- def reset(self):
- self.trailer = None
- self.contents = ''
- self.offsets = []
- self.currentOffset = 0
- self.offsetsStack = []
-
- def readRoot(self):
- result = None
- self.reset()
- # Get the header, make sure it's a valid file.
- if not is_stream_binary_plist(self.file):
- raise NotBinaryPlistException()
- self.file.seek(0)
- self.contents = self.file.read()
- if len(self.contents) < 32:
- raise InvalidPlistException("File is too short.")
- trailerContents = self.contents[-32:]
- try:
- self.trailer = PlistTrailer._make(unpack("!xxxxxxBBQQQ", trailerContents))
-
- if pow(2, self.trailer.offsetSize*8) < self.trailer.offsetTableOffset:
- raise InvalidPlistException("Offset size insufficient to reference all objects.")
-
- if pow(2, self.trailer.objectRefSize*8) < self.trailer.offsetCount:
- raise InvalidPlistException("Too many offsets to represent in size of object reference representation.")
-
- offset_size = self.trailer.offsetSize * self.trailer.offsetCount
- offset = self.trailer.offsetTableOffset
-
- if offset + offset_size > pow(2, 64):
- raise InvalidPlistException("Offset table is excessively long.")
-
- if self.trailer.offsetSize > 16:
- raise InvalidPlistException("Offset size is greater than maximum integer size.")
-
- if self.trailer.objectRefSize == 0:
- raise InvalidPlistException("Object reference size is zero.")
-
- if offset >= len(self.contents) - 32:
- raise InvalidPlistException("Offset table offset is too large.")
-
- if offset < len("bplist00x"):
- raise InvalidPlistException("Offset table offset is too small.")
-
- if self.trailer.topLevelObjectNumber >= self.trailer.offsetCount:
- raise InvalidPlistException("Top level object number is larger than the number of objects.")
-
- offset_contents = self.contents[offset:offset+offset_size]
- offset_i = 0
- offset_table_length = len(offset_contents)
-
- while offset_i < self.trailer.offsetCount:
- begin = self.trailer.offsetSize*offset_i
- end = begin+self.trailer.offsetSize
- if end > offset_table_length:
- raise InvalidPlistException("End of object is at invalid offset %d in offset table of length %d" % (end, offset_table_length))
- tmp_contents = offset_contents[begin:end]
- tmp_sized = self.getSizedInteger(tmp_contents, self.trailer.offsetSize)
- self.offsets.append(tmp_sized)
- offset_i += 1
- self.setCurrentOffsetToObjectNumber(self.trailer.topLevelObjectNumber)
- result = self.readObject()
- except TypeError as e:
- raise InvalidPlistException(e)
- return result
-
- def setCurrentOffsetToObjectNumber(self, objectNumber):
- if objectNumber > len(self.offsets) - 1:
- raise InvalidPlistException("Invalid offset number: %d" % objectNumber)
- self.currentOffset = self.offsets[objectNumber]
- if self.currentOffset in self.offsetsStack:
- raise InvalidPlistException("Recursive data structure detected in object: %d" % objectNumber)
-
- def beginOffsetProtection(self):
- self.offsetsStack.append(self.currentOffset)
- return self.currentOffset
-
- def endOffsetProtection(self, offset):
- try:
- index = self.offsetsStack.index(offset)
- self.offsetsStack = self.offsetsStack[:index]
- except ValueError as e:
- pass
-
- def readObject(self):
- protection = self.beginOffsetProtection()
- result = None
- tmp_byte = self.contents[self.currentOffset:self.currentOffset+1]
- if len(tmp_byte) != 1:
- raise InvalidPlistException("No object found at offset: %d" % self.currentOffset)
- marker_byte = unpack("!B", tmp_byte)[0]
- format = (marker_byte >> 4) & 0x0f
- extra = marker_byte & 0x0f
- self.currentOffset += 1
-
- def proc_extra(extra):
- if extra == 0b1111:
- extra = self.readObject()
- return extra
-
- # bool, null, or fill byte
- if format == 0b0000:
- if extra == 0b0000:
- result = None
- elif extra == 0b1000:
- result = False
- elif extra == 0b1001:
- result = True
- elif extra == 0b1111:
- pass # fill byte
- else:
- raise InvalidPlistException("Invalid object found at offset: %d" % (self.currentOffset - 1))
- # int
- elif format == 0b0001:
- result = self.readInteger(pow(2, extra))
- # real
- elif format == 0b0010:
- result = self.readReal(extra)
- # date
- elif format == 0b0011 and extra == 0b0011:
- result = self.readDate()
- # data
- elif format == 0b0100:
- extra = proc_extra(extra)
- result = self.readData(extra)
- # ascii string
- elif format == 0b0101:
- extra = proc_extra(extra)
- result = self.readAsciiString(extra)
- # Unicode string
- elif format == 0b0110:
- extra = proc_extra(extra)
- result = self.readUnicode(extra)
- # uid
- elif format == 0b1000:
- result = self.readUid(extra)
- # array
- elif format == 0b1010:
- extra = proc_extra(extra)
- result = self.readArray(extra)
- # set
- elif format == 0b1100:
- extra = proc_extra(extra)
- result = set(self.readArray(extra))
- # dict
- elif format == 0b1101:
- extra = proc_extra(extra)
- result = self.readDict(extra)
- else:
- raise InvalidPlistException("Invalid object found: {format: %s, extra: %s}" % (bin(format), bin(extra)))
- self.endOffsetProtection(protection)
- return result
-
- def readContents(self, length, description="Object contents"):
- end = self.currentOffset + length
- if end >= len(self.contents) - 32:
- raise InvalidPlistException("%s extends into trailer" % description)
- elif length < 0:
- raise InvalidPlistException("%s length is less than zero" % length)
- data = self.contents[self.currentOffset:end]
- return data
-
- def readInteger(self, byteSize):
- data = self.readContents(byteSize, "Integer")
- self.currentOffset = self.currentOffset + byteSize
- return self.getSizedInteger(data, byteSize, as_number=True)
-
- def readReal(self, length):
- to_read = pow(2, length)
- data = self.readContents(to_read, "Real")
- if length == 2: # 4 bytes
- result = unpack('>f', data)[0]
- elif length == 3: # 8 bytes
- result = unpack('>d', data)[0]
- else:
- raise InvalidPlistException("Unknown Real of length %d bytes" % to_read)
- return result
-
- def readRefs(self, count):
- refs = []
- i = 0
- while i < count:
- fragment = self.readContents(self.trailer.objectRefSize, "Object reference")
- ref = self.getSizedInteger(fragment, len(fragment))
- refs.append(ref)
- self.currentOffset += self.trailer.objectRefSize
- i += 1
- return refs
-
- def readArray(self, count):
- if not isinstance(count, (int, long)):
- raise InvalidPlistException("Count of entries in dict isn't of integer type.")
- result = []
- values = self.readRefs(count)
- i = 0
- while i < len(values):
- self.setCurrentOffsetToObjectNumber(values[i])
- value = self.readObject()
- result.append(value)
- i += 1
- return result
-
- def readDict(self, count):
- if not isinstance(count, (int, long)):
- raise InvalidPlistException("Count of keys/values in dict isn't of integer type.")
- result = {}
- keys = self.readRefs(count)
- values = self.readRefs(count)
- i = 0
- while i < len(keys):
- self.setCurrentOffsetToObjectNumber(keys[i])
- key = self.readObject()
- self.setCurrentOffsetToObjectNumber(values[i])
- value = self.readObject()
- result[key] = value
- i += 1
- return result
-
- def readAsciiString(self, length):
- if not isinstance(length, (int, long)):
- raise InvalidPlistException("Length of ASCII string isn't of integer type.")
- data = self.readContents(length, "ASCII string")
- result = unpack("!%ds" % length, data)[0]
- self.currentOffset += length
- return str(result.decode('ascii'))
-
- def readUnicode(self, length):
- if not isinstance(length, (int, long)):
- raise InvalidPlistException("Length of Unicode string isn't of integer type.")
- actual_length = length*2
- data = self.readContents(actual_length, "Unicode string")
- self.currentOffset += actual_length
- return data.decode('utf_16_be')
-
- def readDate(self):
- data = self.readContents(8, "Date")
- x = unpack(">d", data)[0]
- if math.isnan(x):
- raise InvalidPlistException("Date is NaN")
- # Use timedelta to workaround time_t size limitation on 32-bit python.
- try:
- result = datetime.timedelta(seconds=x) + apple_reference_date
- except OverflowError:
- if x > 0:
- result = datetime.datetime.max
- else:
- result = datetime.datetime.min
- self.currentOffset += 8
- return result
-
- def readData(self, length):
- if not isinstance(length, (int, long)):
- raise InvalidPlistException("Length of data isn't of integer type.")
- result = self.readContents(length, "Data")
- self.currentOffset += length
- return Data(result)
-
- def readUid(self, length):
- if not isinstance(length, (int, long)):
- raise InvalidPlistException("Uid length isn't of integer type.")
- return Uid(self.readInteger(length+1))
-
- def getSizedInteger(self, data, byteSize, as_number=False):
- """Numbers of 8 bytes are signed integers when they refer to numbers, but unsigned otherwise."""
- result = 0
- if byteSize == 0:
- raise InvalidPlistException("Encountered integer with byte size of 0.")
- # 1, 2, and 4 byte integers are unsigned
- elif byteSize == 1:
- result = unpack('>B', data)[0]
- elif byteSize == 2:
- result = unpack('>H', data)[0]
- elif byteSize == 4:
- result = unpack('>L', data)[0]
- elif byteSize == 8:
- if as_number:
- result = unpack('>q', data)[0]
- else:
- result = unpack('>Q', data)[0]
- elif byteSize <= 16:
- # Handle odd-sized or integers larger than 8 bytes
- # Don't naively go over 16 bytes, in order to prevent infinite loops.
- result = 0
- if hasattr(int, 'from_bytes'):
- result = int.from_bytes(data, 'big')
- else:
- for byte in data:
- if not isinstance(byte, int): # Python3.0-3.1.x return ints, 2.x return str
- byte = unpack_from('>B', byte)[0]
- result = (result << 8) | byte
- else:
- raise InvalidPlistException("Encountered integer longer than 16 bytes.")
- return result
-
-class HashableWrapper(object):
- def __init__(self, value):
- self.value = value
- def __repr__(self):
- return "<HashableWrapper: %s>" % [self.value]
-
-class BoolWrapper(object):
- def __init__(self, value):
- self.value = value
- def __repr__(self):
- return "<BoolWrapper: %s>" % self.value
-
-class FloatWrapper(object):
- _instances = {}
- def __new__(klass, value):
- # Ensure FloatWrapper(x) for a given float x is always the same object
- wrapper = klass._instances.get(value)
- if wrapper is None:
- wrapper = object.__new__(klass)
- wrapper.value = value
- klass._instances[value] = wrapper
- return wrapper
- def __repr__(self):
- return "<FloatWrapper: %s>" % self.value
-
-class StringWrapper(object):
- __instances = {}
-
- encodedValue = None
- encoding = None
-
- def __new__(cls, value):
- '''Ensure we only have a only one instance for any string,
- and that we encode ascii as 1-byte-per character when possible'''
-
- encodedValue = None
-
- for encoding in ('ascii', 'utf_16_be'):
- try:
- encodedValue = value.encode(encoding)
- except: pass
- if encodedValue is not None:
- if encodedValue not in cls.__instances:
- cls.__instances[encodedValue] = super(StringWrapper, cls).__new__(cls)
- cls.__instances[encodedValue].encodedValue = encodedValue
- cls.__instances[encodedValue].encoding = encoding
- return cls.__instances[encodedValue]
-
- raise ValueError('Unable to get ascii or utf_16_be encoding for %s' % repr(value))
-
- def __len__(self):
- '''Return roughly the number of characters in this string (half the byte length)'''
- if self.encoding == 'ascii':
- return len(self.encodedValue)
- else:
- return len(self.encodedValue)//2
-
- def __lt__(self, other):
- return self.encodedValue < other.encodedValue
-
- @property
- def encodingMarker(self):
- if self.encoding == 'ascii':
- return 0b0101
- else:
- return 0b0110
-
- def __repr__(self):
- return '<StringWrapper (%s): %s>' % (self.encoding, self.encodedValue)
-
-class PlistWriter(object):
- header = b'bplist00bybiplist1.0'
- file = None
- byteCounts = None
- trailer = None
- computedUniques = None
- writtenReferences = None
- referencePositions = None
- wrappedTrue = None
- wrappedFalse = None
- # Used to detect recursive object references.
- objectsStack = []
-
- def __init__(self, file):
- self.reset()
- self.file = file
- self.wrappedTrue = BoolWrapper(True)
- self.wrappedFalse = BoolWrapper(False)
-
- def reset(self):
- self.byteCounts = PlistByteCounts(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
- self.trailer = PlistTrailer(0, 0, 0, 0, 0)
-
- # A set of all the uniques which have been computed.
- self.computedUniques = set()
- # A list of all the uniques which have been written.
- self.writtenReferences = {}
- # A dict of the positions of the written uniques.
- self.referencePositions = {}
-
- self.objectsStack = []
-
- def positionOfObjectReference(self, obj):
- """If the given object has been written already, return its
- position in the offset table. Otherwise, return None."""
- return self.writtenReferences.get(obj)
-
- def writeRoot(self, root):
- """
- Strategy is:
- - write header
- - wrap root object so everything is hashable
- - compute size of objects which will be written
- - need to do this in order to know how large the object refs
- will be in the list/dict/set reference lists
- - write objects
- - keep objects in writtenReferences
- - keep positions of object references in referencePositions
- - write object references with the length computed previously
- - computer object reference length
- - write object reference positions
- - write trailer
- """
- output = self.header
- wrapped_root = self.wrapRoot(root)
- self.computeOffsets(wrapped_root, asReference=True, isRoot=True)
- self.trailer = self.trailer._replace(**{'objectRefSize':self.intSize(len(self.computedUniques))})
- self.writeObjectReference(wrapped_root, output)
- output = self.writeObject(wrapped_root, output, setReferencePosition=True)
-
- # output size at this point is an upper bound on how big the
- # object reference offsets need to be.
- self.trailer = self.trailer._replace(**{
- 'offsetSize':self.intSize(len(output)),
- 'offsetCount':len(self.computedUniques),
- 'offsetTableOffset':len(output),
- 'topLevelObjectNumber':0
- })
-
- output = self.writeOffsetTable(output)
- output += pack('!xxxxxxBBQQQ', *self.trailer)
- self.file.write(output)
-
- def beginRecursionProtection(self, obj):
- if not isinstance(obj, (set, dict, list, tuple)):
- return
- if id(obj) in self.objectsStack:
- raise InvalidPlistException("Recursive containers are not allowed in plists.")
- self.objectsStack.append(id(obj))
-
- def endRecursionProtection(self, obj):
- if not isinstance(obj, (set, dict, list, tuple)):
- return
- try:
- index = self.objectsStack.index(id(obj))
- self.objectsStack = self.objectsStack[:index]
- except ValueError as e:
- pass
-
- def wrapRoot(self, root):
- result = None
- self.beginRecursionProtection(root)
-
- if isinstance(root, bool):
- if root is True:
- result = self.wrappedTrue
- else:
- result = self.wrappedFalse
- elif isinstance(root, float):
- result = FloatWrapper(root)
- elif isinstance(root, set):
- n = set()
- for value in root:
- n.add(self.wrapRoot(value))
- result = HashableWrapper(n)
- elif isinstance(root, dict):
- n = {}
- for key, value in iteritems(root):
- n[self.wrapRoot(key)] = self.wrapRoot(value)
- result = HashableWrapper(n)
- elif isinstance(root, list):
- n = []
- for value in root:
- n.append(self.wrapRoot(value))
- result = HashableWrapper(n)
- elif isinstance(root, tuple):
- n = tuple([self.wrapRoot(value) for value in root])
- result = HashableWrapper(n)
- elif isinstance(root, (str, unicode)) and not isinstance(root, Data):
- result = StringWrapper(root)
- elif isinstance(root, bytes):
- result = Data(root)
- else:
- result = root
-
- self.endRecursionProtection(root)
- return result
-
- def incrementByteCount(self, field, incr=1):
- self.byteCounts = self.byteCounts._replace(**{field:self.byteCounts.__getattribute__(field) + incr})
-
- def computeOffsets(self, obj, asReference=False, isRoot=False):
- def check_key(key):
- if key is None:
- raise InvalidPlistException('Dictionary keys cannot be null in plists.')
- elif isinstance(key, Data):
- raise InvalidPlistException('Data cannot be dictionary keys in plists.')
- elif not isinstance(key, StringWrapper):
- raise InvalidPlistException('Keys must be strings.')
-
- def proc_size(size):
- if size > 0b1110:
- size += self.intSize(size)
- return size
- # If this should be a reference, then we keep a record of it in the
- # uniques table.
- if asReference:
- if obj in self.computedUniques:
- return
- else:
- self.computedUniques.add(obj)
-
- if obj is None:
- self.incrementByteCount('nullBytes')
- elif isinstance(obj, BoolWrapper):
- self.incrementByteCount('boolBytes')
- elif isinstance(obj, Uid):
- size = self.intSize(obj.integer)
- self.incrementByteCount('uidBytes', incr=1+size)
- elif isinstance(obj, (int, long)):
- size = self.intSize(obj)
- self.incrementByteCount('intBytes', incr=1+size)
- elif isinstance(obj, FloatWrapper):
- size = self.realSize(obj)
- self.incrementByteCount('realBytes', incr=1+size)
- elif isinstance(obj, datetime.datetime):
- self.incrementByteCount('dateBytes', incr=2)
- elif isinstance(obj, Data):
- size = proc_size(len(obj))
- self.incrementByteCount('dataBytes', incr=1+size)
- elif isinstance(obj, StringWrapper):
- size = proc_size(len(obj))
- self.incrementByteCount('stringBytes', incr=1+size)
- elif isinstance(obj, HashableWrapper):
- obj = obj.value
- if isinstance(obj, set):
- size = proc_size(len(obj))
- self.incrementByteCount('setBytes', incr=1+size)
- for value in obj:
- self.computeOffsets(value, asReference=True)
- elif isinstance(obj, (list, tuple)):
- size = proc_size(len(obj))
- self.incrementByteCount('arrayBytes', incr=1+size)
- for value in obj:
- asRef = True
- self.computeOffsets(value, asReference=True)
- elif isinstance(obj, dict):
- size = proc_size(len(obj))
- self.incrementByteCount('dictBytes', incr=1+size)
- for key, value in iteritems(obj):
- check_key(key)
- self.computeOffsets(key, asReference=True)
- self.computeOffsets(value, asReference=True)
- else:
- raise InvalidPlistException("Unknown object type: %s (%s)" % (type(obj).__name__, repr(obj)))
-
- def writeObjectReference(self, obj, output):
- """Tries to write an object reference, adding it to the references
- table. Does not write the actual object bytes or set the reference
- position. Returns a tuple of whether the object was a new reference
- (True if it was, False if it already was in the reference table)
- and the new output.
- """
- position = self.positionOfObjectReference(obj)
- if position is None:
- self.writtenReferences[obj] = len(self.writtenReferences)
- output += self.binaryInt(len(self.writtenReferences) - 1, byteSize=self.trailer.objectRefSize)
- return (True, output)
- else:
- output += self.binaryInt(position, byteSize=self.trailer.objectRefSize)
- return (False, output)
-
- def writeObject(self, obj, output, setReferencePosition=False):
- """Serializes the given object to the output. Returns output.
- If setReferencePosition is True, will set the position the
- object was written.
- """
- def proc_variable_length(format, length):
- result = b''
- if length > 0b1110:
- result += pack('!B', (format << 4) | 0b1111)
- result = self.writeObject(length, result)
- else:
- result += pack('!B', (format << 4) | length)
- return result
-
- def timedelta_total_seconds(td):
- # Shim for Python 2.6 compatibility, which doesn't have total_seconds.
- # Make one argument a float to ensure the right calculation.
- return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10.0**6) / 10.0**6
-
- if setReferencePosition:
- self.referencePositions[obj] = len(output)
-
- if obj is None:
- output += pack('!B', 0b00000000)
- elif isinstance(obj, BoolWrapper):
- if obj.value is False:
- output += pack('!B', 0b00001000)
- else:
- output += pack('!B', 0b00001001)
- elif isinstance(obj, Uid):
- size = self.intSize(obj.integer)
- output += pack('!B', (0b1000 << 4) | size - 1)
- output += self.binaryInt(obj.integer)
- elif isinstance(obj, (int, long)):
- byteSize = self.intSize(obj)
- root = math.log(byteSize, 2)
- output += pack('!B', (0b0001 << 4) | int(root))
- output += self.binaryInt(obj, as_number=True)
- elif isinstance(obj, FloatWrapper):
- # just use doubles
- output += pack('!B', (0b0010 << 4) | 3)
- output += self.binaryReal(obj)
- elif isinstance(obj, datetime.datetime):
- try:
- timestamp = (obj - apple_reference_date).total_seconds()
- except AttributeError:
- timestamp = timedelta_total_seconds(obj - apple_reference_date)
- output += pack('!B', 0b00110011)
- output += pack('!d', float(timestamp))
- elif isinstance(obj, Data):
- output += proc_variable_length(0b0100, len(obj))
- output += obj
- elif isinstance(obj, StringWrapper):
- output += proc_variable_length(obj.encodingMarker, len(obj))
- output += obj.encodedValue
- elif isinstance(obj, bytes):
- output += proc_variable_length(0b0101, len(obj))
- output += obj
- elif isinstance(obj, HashableWrapper):
- obj = obj.value
- if isinstance(obj, (set, list, tuple)):
- if isinstance(obj, set):
- output += proc_variable_length(0b1100, len(obj))
- else:
- output += proc_variable_length(0b1010, len(obj))
-
- objectsToWrite = []
- for objRef in sorted(obj) if isinstance(obj, set) else obj:
- (isNew, output) = self.writeObjectReference(objRef, output)
- if isNew:
- objectsToWrite.append(objRef)
- for objRef in objectsToWrite:
- output = self.writeObject(objRef, output, setReferencePosition=True)
- elif isinstance(obj, dict):
- output += proc_variable_length(0b1101, len(obj))
- keys = []
- values = []
- objectsToWrite = []
- for key, value in sorted(iteritems(obj)):
- keys.append(key)
- values.append(value)
- for key in keys:
- (isNew, output) = self.writeObjectReference(key, output)
- if isNew:
- objectsToWrite.append(key)
- for value in values:
- (isNew, output) = self.writeObjectReference(value, output)
- if isNew:
- objectsToWrite.append(value)
- for objRef in objectsToWrite:
- output = self.writeObject(objRef, output, setReferencePosition=True)
- return output
-
- def writeOffsetTable(self, output):
- """Writes all of the object reference offsets."""
- all_positions = []
- writtenReferences = list(self.writtenReferences.items())
- writtenReferences.sort(key=lambda x: x[1])
- for obj,order in writtenReferences:
- # Porting note: Elsewhere we deliberately replace empty unicdoe strings
- # with empty binary strings, but the empty unicode string
- # goes into writtenReferences. This isn't an issue in Py2
- # because u'' and b'' have the same hash; but it is in
- # Py3, where they don't.
- if bytes != str and obj == unicodeEmpty:
- obj = b''
- position = self.referencePositions.get(obj)
- if position is None:
- raise InvalidPlistException("Error while writing offsets table. Object not found. %s" % obj)
- output += self.binaryInt(position, self.trailer.offsetSize)
- all_positions.append(position)
- return output
-
- def binaryReal(self, obj):
- # just use doubles
- result = pack('>d', obj.value)
- return result
-
- def binaryInt(self, obj, byteSize=None, as_number=False):
- result = b''
- if byteSize is None:
- byteSize = self.intSize(obj)
- if byteSize == 1:
- result += pack('>B', obj)
- elif byteSize == 2:
- result += pack('>H', obj)
- elif byteSize == 4:
- result += pack('>L', obj)
- elif byteSize == 8:
- if as_number:
- result += pack('>q', obj)
- else:
- result += pack('>Q', obj)
- elif byteSize <= 16:
- try:
- result = pack('>Q', 0) + pack('>Q', obj)
- except struct_error as e:
- raise InvalidPlistException("Unable to pack integer %d: %s" % (obj, e))
- else:
- raise InvalidPlistException("Core Foundation can't handle integers with size greater than 16 bytes.")
- return result
-
- def intSize(self, obj):
- """Returns the number of bytes necessary to store the given integer."""
- # SIGNED
- if obj < 0: # Signed integer, always 8 bytes
- return 8
- # UNSIGNED
- elif obj <= 0xFF: # 1 byte
- return 1
- elif obj <= 0xFFFF: # 2 bytes
- return 2
- elif obj <= 0xFFFFFFFF: # 4 bytes
- return 4
- # SIGNED
- # 0x7FFFFFFFFFFFFFFF is the max.
- elif obj <= 0x7FFFFFFFFFFFFFFF: # 8 bytes signed
- return 8
- elif obj <= 0xffffffffffffffff: # 8 bytes unsigned
- return 16
- else:
- raise InvalidPlistException("Core Foundation can't handle integers with size greater than 8 bytes.")
-
- def realSize(self, obj):
- return 8
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/biplist/qt_attribution.json b/src/3rdparty/python/lib/python2.7/site-packages/biplist/qt_attribution.json
deleted file mode 100644
index 266e9dd89..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/biplist/qt_attribution.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "Id": "biplist",
- "Name": "biplist",
- "QDocModule": "qbs",
- "QtUsage": "Used in the qbs dmg module for building Apple disk images.",
- "Description": "biplist is a library for reading/writing binary plists.",
- "Homepage": "https://bitbucket.org/wooster/biplist",
- "Version": "1.0.2",
- "License": "BSD 3-clause \"New\" or \"Revised\" License",
- "LicenseId": "BSD-3-Clause",
- "LicenseFile": "LICENSE",
- "Copyright": "Copyright (c) 2010, Andrew Wooster"
-}
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/__init__.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/__init__.py
deleted file mode 100644
index e7f985c32..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .core import build_dmg
-
-__all__ = ['dmgbuild']
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/badge.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/badge.py
deleted file mode 100644
index 159a53708..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/badge.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from Quartz import *
-import math
-
-_REMOVABLE_DISK_PATH = '/System/Library/Extensions/IOStorageFamily.kext/Contents/Resources/Removable.icns'
-
-def badge_disk_icon(badge_file, output_file):
- # Load the Removable disk icon
- url = CFURLCreateWithFileSystemPath(None, _REMOVABLE_DISK_PATH,
- kCFURLPOSIXPathStyle, False)
- backdrop = CGImageSourceCreateWithURL(url, None)
- backdropCount = CGImageSourceGetCount(backdrop)
-
- # Load the badge
- url = CFURLCreateWithFileSystemPath(None, badge_file,
- kCFURLPOSIXPathStyle, False)
- badge = CGImageSourceCreateWithURL(url, None)
- assert badge is not None, 'Unable to process image file: %s' % badge_file
- badgeCount = CGImageSourceGetCount(badge)
-
- # Set up a destination for our target
- url = CFURLCreateWithFileSystemPath(None, output_file,
- kCFURLPOSIXPathStyle, False)
- target = CGImageDestinationCreateWithURL(url, 'com.apple.icns',
- backdropCount, None)
-
- # Get the RGB colorspace
- rgbColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)
-
- # Scale
- scale = 1.0
-
- # Perspective transform
- corners = ((0.2, 0.95), (0.8, 0.95), (0.85, 0.35), (0.15, 0.35))
-
- # Translation
- position = (0.5, 0.5)
-
- for n in range(backdropCount):
- props = CGImageSourceCopyPropertiesAtIndex(backdrop, n, None)
- width = props['PixelWidth']
- height = props['PixelHeight']
- dpi = props['DPIWidth']
- depth = props['Depth']
-
- # Choose the best sized badge image
- bestWidth = None
- bestHeight = None
- bestBadge = None
- bestDepth = None
- bestDPI = None
- for m in range(badgeCount):
- badgeProps = CGImageSourceCopyPropertiesAtIndex(badge, m, None)
- badgeWidth = badgeProps['PixelWidth']
- badgeHeight = badgeProps['PixelHeight']
- badgeDPI = badgeProps['DPIWidth']
- badgeDepth = badgeProps['Depth']
-
- if bestBadge is None or (badgeWidth <= width
- and (bestWidth > width
- or badgeWidth > bestWidth
- or (badgeWidth == bestWidth
- and badgeDPI == dpi
- and badgeDepth <= depth
- and (bestDepth is None
- or badgeDepth > bestDepth)))):
- bestBadge = m
- bestWidth = badgeWidth
- bestHeight = badgeHeight
- bestDPI = badgeDPI
- bestDepth = badgeDepth
-
- badgeImage = CGImageSourceCreateImageAtIndex(badge, bestBadge, None)
- badgeCI = CIImage.imageWithCGImage_(badgeImage)
-
- backgroundImage = CGImageSourceCreateImageAtIndex(backdrop, n, None)
- backgroundCI = CIImage.imageWithCGImage_(backgroundImage)
-
- compositor = CIFilter.filterWithName_('CISourceOverCompositing')
- lanczos = CIFilter.filterWithName_('CILanczosScaleTransform')
- perspective = CIFilter.filterWithName_('CIPerspectiveTransform')
- transform = CIFilter.filterWithName_('CIAffineTransform')
-
- lanczos.setValue_forKey_(badgeCI, kCIInputImageKey)
- lanczos.setValue_forKey_(scale * float(width)/bestWidth, kCIInputScaleKey)
- lanczos.setValue_forKey_(1.0, kCIInputAspectRatioKey)
-
- topLeft = (width * scale * corners[0][0],
- width * scale * corners[0][1])
- topRight = (width * scale * corners[1][0],
- width * scale * corners[1][1])
- bottomRight = (width * scale * corners[2][0],
- width * scale * corners[2][1])
- bottomLeft = (width * scale * corners[3][0],
- width * scale * corners[3][1])
-
- out = lanczos.valueForKey_(kCIOutputImageKey)
- if width >= 16:
- perspective.setValue_forKey_(out, kCIInputImageKey)
- perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*topLeft),
- 'inputTopLeft')
- perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*topRight),
- 'inputTopRight')
- perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*bottomRight),
- 'inputBottomRight')
- perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*bottomLeft),
- 'inputBottomLeft')
- out = perspective.valueForKey_(kCIOutputImageKey)
-
- tfm = NSAffineTransform.transform()
- tfm.translateXBy_yBy_(math.floor((position[0] - 0.5 * scale) * width),
- math.floor((position[1] - 0.5 * scale) * height))
-
- transform.setValue_forKey_(out, kCIInputImageKey)
- transform.setValue_forKey_(tfm, 'inputTransform')
- out = transform.valueForKey_(kCIOutputImageKey)
-
- compositor.setValue_forKey_(out, kCIInputImageKey)
- compositor.setValue_forKey_(backgroundCI, kCIInputBackgroundImageKey)
-
- result = compositor.valueForKey_(kCIOutputImageKey)
-
- cgContext = CGBitmapContextCreate(None,
- width,
- height,
- 8,
- 0,
- rgbColorSpace,
- kCGImageAlphaPremultipliedLast)
- context = CIContext.contextWithCGContext_options_(cgContext, None)
-
- context.drawImage_inRect_fromRect_(result,
- ((0, 0), (width, height)),
- ((0, 0), (width, height)))
-
- image = CGBitmapContextCreateImage(cgContext)
-
- CGImageDestinationAddImage(target, image, props)
-
- CGImageDestinationFinalize(target)
-
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/colors.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/colors.py
deleted file mode 100644
index 891e5d536..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/colors.py
+++ /dev/null
@@ -1,498 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import re
-import math
-
-class Color (object):
- def to_rgb(self):
- raise Exception('Must implement to_rgb() in subclasses')
-
-class RGB (Color):
- def __init__(self, r, g, b):
- self.r = r
- self.g = g
- self.b = b
-
- def to_rgb(self):
- return self
-
-class HSL (Color):
- def __init__(self, h, s, l):
- self.h = h
- self.s = s
- self.l = l
-
- @staticmethod
- def _hue_to_rgb(t1, t2, hue):
- if hue < 0:
- hue += 6
- elif hue >= 6:
- hue -= 6
-
- if hue < 1:
- return (t2 - t1) * hue + t1
- elif hue < 3:
- return t2
- elif hue < 4:
- return (t2 - t1) * (4 - hue) + t1
- else:
- return t1
-
- def to_rgb(self):
- hue = self.h / 60.0
- if self.l <= 0.5:
- t2 = self.l * (self.s + 1)
- else:
- t2 = self.l + self.s - (self.l * self.s)
- t1 = self.l * 2 - t2
- r = self._hue_to_rgb(t1, t2, hue + 2)
- g = self._hue_to_rgb(t1, t2, hue)
- b = self._hue_to_rgb(t1, t2, hue - 2)
- return RGB(r, g, b)
-
-class HWB (Color):
- def __init__(self, h, w, b):
- self.h = h
- self.w = w
- self.b = b
-
- @staticmethod
- def _hue_to_rgb(hue):
- if hue < 0:
- hue += 6
- elif hue >= 6:
- hue -= 6
-
- if hue < 1:
- return hue
- elif hue < 3:
- return 1
- elif hue < 4:
- return (4 - hue)
- else:
- return 0
-
- def to_rgb(self):
- hue = self.h / 60.0
- t1 = 1 - self.w - self.b
- r = self._hue_to_rgb(hue + 2) * t1 + self.w
- g = self._hue_to_rgb(hue) * t1 + self.w
- b = self._hue_to_rgb(hue - 2) * t1 + self.w
- return RGB(r, g, b)
-
-class CMYK (Color):
- def __init__(self, c, m, y, k):
- self.c = c
- self.m = m
- self.y = y
- self.k = k
-
- def to_rgb(self):
- r = 1.0 - min(1.0, self.c + self.k)
- g = 1.0 - min(1.0, self.m + self.k)
- b = 1.0 - min(1.0, self.y + self.k)
- return RGB(r, g, b)
-
-class Gray (Color):
- def __init__(self, g):
- self.g = g
-
- def to_rgb(self):
- return RGB(g, g, g)
-
-_x11_colors = {
- 'aliceblue': (240, 248, 255),
- 'antiquewhite': (250, 235, 215),
- 'aqua': ( 0, 255, 255),
- 'aquamarine': (127, 255, 212),
- 'azure': (240, 255, 255),
- 'beige': (245, 245, 220),
- 'bisque': (255, 228, 196),
- 'black': ( 0, 0, 0),
- 'blanchedalmond': (255, 235, 205),
- 'blue': ( 0, 0, 255),
- 'blueviolet': (138, 43, 226),
- 'brown': (165, 42, 42),
- 'burlywood': (222, 184, 135),
- 'cadetblue': ( 95, 158, 160),
- 'chartreuse': (127, 255, 0),
- 'chocolate': (210, 105, 30),
- 'coral': (255, 127, 80),
- 'cornflowerblue': (100, 149, 237),
- 'cornsilk': (255, 248, 220),
- 'crimson': (220, 20, 60),
- 'cyan': ( 0, 255, 255),
- 'darkblue': ( 0, 0, 139),
- 'darkcyan': ( 0, 139, 139),
- 'darkgoldenrod': (184, 134, 11),
- 'darkgray': (169, 169, 169),
- 'darkgreen': ( 0, 100, 0),
- 'darkgrey': (169, 169, 169),
- 'darkkhaki': (189, 183, 107),
- 'darkmagenta': (139, 0, 139),
- 'darkolivegreen': ( 85, 107, 47),
- 'darkorange': (255, 140, 0),
- 'darkorchid': (153, 50, 204),
- 'darkred': (139, 0, 0),
- 'darksalmon': (233, 150, 122),
- 'darkseagreen': (143, 188, 143),
- 'darkslateblue': ( 72, 61, 139),
- 'darkslategray': ( 47, 79, 79),
- 'darkslategrey': ( 47, 79, 79),
- 'darkturquoise': ( 0, 206, 209),
- 'darkviolet': (148, 0, 211),
- 'deeppink': (255, 20, 147),
- 'deepskyblue': ( 0, 191, 255),
- 'dimgray': (105, 105, 105),
- 'dimgrey': (105, 105, 105),
- 'dodgerblue': ( 30, 144, 255),
- 'firebrick': (178, 34, 34),
- 'floralwhite': (255, 250, 240),
- 'forestgreen': ( 34, 139, 34),
- 'fuchsia': (255, 0, 255),
- 'gainsboro': (220, 220, 220),
- 'ghostwhite': (248, 248, 255),
- 'gold': (255, 215, 0),
- 'goldenrod': (218, 165, 32),
- 'gray': (128, 128, 128),
- 'grey': (128, 128, 128),
- 'green': ( 0, 128, 0),
- 'greenyellow': (173, 255, 47),
- 'honeydew': (240, 255, 240),
- 'hotpink': (255, 105, 180),
- 'indianred': (205, 92, 92),
- 'indigo': ( 75, 0, 130),
- 'ivory': (255, 255, 240),
- 'khaki': (240, 230, 140),
- 'lavender': (230, 230, 250),
- 'lavenderblush': (255, 240, 245),
- 'lawngreen': (124, 252, 0),
- 'lemonchiffon': (255, 250, 205),
- 'lightblue': (173, 216, 230),
- 'lightcoral': (240, 128, 128),
- 'lightcyan': (224, 255, 255),
- 'lightgoldenrodyellow': (250, 250, 210),
- 'lightgray': (211, 211, 211),
- 'lightgreen': (144, 238, 144),
- 'lightgrey': (211, 211, 211),
- 'lightpink': (255, 182, 193),
- 'lightsalmon': (255, 160, 122),
- 'lightseagreen': ( 32, 178, 170),
- 'lightskyblue': (135, 206, 250),
- 'lightslategray': (119, 136, 153),
- 'lightslategrey': (119, 136, 153),
- 'lightsteelblue': (176, 196, 222),
- 'lightyellow': (255, 255, 224),
- 'lime': ( 0, 255, 0),
- 'limegreen': ( 50, 205, 50),
- 'linen': (250, 240, 230),
- 'magenta': (255, 0, 255),
- 'maroon': (128, 0, 0),
- 'mediumaquamarine': (102, 205, 170),
- 'mediumblue': ( 0, 0, 205),
- 'mediumorchid': (186, 85, 211),
- 'mediumpurple': (147, 112, 219),
- 'mediumseagreen': ( 60, 179, 113),
- 'mediumslateblue': (123, 104, 238),
- 'mediumspringgreen': ( 0, 250, 154),
- 'mediumturquoise': ( 72, 209, 204),
- 'mediumvioletred': (199, 21, 133),
- 'midnightblue': ( 25, 25, 112),
- 'mintcream': (245, 255, 250),
- 'mistyrose': (255, 228, 225),
- 'moccasin': (255, 228, 181),
- 'navajowhite': (255, 222, 173),
- 'navy': ( 0, 0, 128),
- 'oldlace': (253, 245, 230),
- 'olive': (128, 128, 0),
- 'olivedrab': (107, 142, 35),
- 'orange': (255, 165, 0),
- 'orangered': (255, 69, 0),
- 'orchid': (218, 112, 214),
- 'palegoldenrod': (238, 232, 170),
- 'palegreen': (152, 251, 152),
- 'paleturquoise': (175, 238, 238),
- 'palevioletred': (219, 112, 147),
- 'papayawhip': (255, 239, 213),
- 'peachpuff': (255, 218, 185),
- 'peru': (205, 133, 63),
- 'pink': (255, 192, 203),
- 'plum': (221, 160, 221),
- 'powderblue': (176, 224, 230),
- 'purple': (128, 0, 128),
- 'red': (255, 0, 0),
- 'rosybrown': (188, 143, 143),
- 'royalblue': ( 65, 105, 225),
- 'saddlebrown': (139, 69, 19),
- 'salmon': (250, 128, 114),
- 'sandybrown': (244, 164, 96),
- 'seagreen': ( 46, 139, 87),
- 'seashell': (255, 245, 238),
- 'sienna': (160, 82, 45),
- 'silver': (192, 192, 192),
- 'skyblue': (135, 206, 235),
- 'slateblue': (106, 90, 205),
- 'slategray': (112, 128, 144),
- 'slategrey': (112, 128, 144),
- 'snow': (255, 250, 250),
- 'springgreen': ( 0, 255, 127),
- 'steelblue': ( 70, 130, 180),
- 'tan': (210, 180, 140),
- 'teal': ( 0, 128, 128),
- 'thistle': (216, 191, 216),
- 'tomato': (255, 99, 71),
- 'turquoise': ( 64, 224, 208),
- 'violet': (238, 130, 238),
- 'wheat': (245, 222, 179),
- 'white': (255, 255, 255),
- 'whitesmoke': (245, 245, 245),
- 'yellow': (255, 255, 0),
- 'yellowgreen': (154, 205, 50)
- }
-
-_ws_re = re.compile(r'\s+')
-_token_re = re.compile(r'[A-Za-z_][A-Za-z0-9_]*')
-_hex_re = re.compile(r'#([0-9a-f]{3}(?:[0-9a-f]{3})?)$')
-_number_re = re.compile(r'[0-9]*(\.[0-9]*)')
-
-
-class ColorParser (object):
- def __init__(self, s):
- self._string = s
- self._pos = 0
-
- def skipws(self):
- m = _ws_re.match(self._string, self._pos)
- if m:
- self._pos = m.end(0)
-
- def expect(self, s, context=''):
- if len(self._string) - self._pos < len(s) \
- or self._string[self._pos:self._pos + len(s)] != s:
- raise ValueError('bad color "%s" - expected "%s"%s'
- % (self._string, s, context))
- self._pos += len(s)
-
- def expectEnd(self):
- if self._pos != len(self._string):
- raise ValueError('junk at end of color "%s"' % self._string)
-
- def getToken(self):
- m = _token_re.match(self._string, self._pos)
- if m:
- token = m.group(0)
-
- self._pos = m.end(0)
- return token
- return None
-
- def parseNumber(self, context=''):
- m = _number_re.match(self._string, self._pos)
- if m:
- self._pos = m.end(0)
- return float(m.group(0))
- raise ValueError('bad color "%s" - expected a number%s'
- % (self._string, context))
-
- def parseColor(self):
- self.skipws()
-
- token = self.getToken()
- if token:
- if token == 'rgb':
- return self.parseRGB()
- elif token == 'hsl':
- return self.parseHSL()
- elif token == 'hwb':
- return self.parseHWB()
- elif token == 'cmyk':
- return self.parseCMYK()
- elif token == 'gray' or token == 'grey':
- return self.parseGray()
-
- try:
- r, g, b = _x11_colors[token]
- except KeyError:
- raise ValueError('unknown color name "%s"' % token)
-
- self.expectEnd()
-
- return RGB(r / 255.0, g / 255.0, b / 255.0)
-
- m = _hex_re.match(self._string, self._pos)
- if m:
- hrgb = m.group(1)
-
- if len(hrgb) == 3:
- r = int('0x' + 2 * hrgb[0], 16)
- g = int('0x' + 2 * hrgb[1], 16)
- b = int('0x' + 2 * hrgb[2], 16)
- else:
- r = int('0x' + hrgb[0:2], 16)
- g = int('0x' + hrgb[2:4], 16)
- b = int('0x' + hrgb[4:6], 16)
-
- self._pos = m.end(0)
- self.skipws()
-
- self.expectEnd()
-
- return RGB(r / 255.0, g / 255.0, b / 255.0)
-
- raise ValueError('bad color syntax "%s"' % self._string)
-
- def parseRGB(self):
- self.expect('(', 'after "rgb"')
- self.skipws()
-
- r = self.parseValue()
-
- self.skipws()
- self.expect(',', 'in "rgb"')
- self.skipws()
-
- g = self.parseValue()
-
- self.skipws()
- self.expect(',', 'in "rgb"')
- self.skipws()
-
- b = self.parseValue()
-
- self.skipws()
- self.expect(')', 'at end of "rgb"')
-
- self.skipws()
- self.expectEnd()
-
- return RGB(r, g, b)
-
- def parseHSL(self):
- self.expect('(', 'after "hsl"')
- self.skipws()
-
- h = self.parseAngle()
-
- self.skipws()
- self.expect(',', 'in "hsl"')
- self.skipws()
-
- s = self.parseValue()
-
- self.skipws()
- self.expect(',', 'in "hsl"')
- self.skipws()
-
- l = self.parseValue()
-
- self.skipws()
- self.expect(')', 'at end of "hsl"')
-
- self.skipws()
- self.expectEnd()
-
- return HSL(h, s, l)
-
- def parseHWB(self):
- self.expect('(', 'after "hwb"')
- self.skipws()
-
- h = self.parseAngle()
-
- self.skipws()
- self.expect(',', 'in "hwb"')
- self.skipws()
-
- w = self.parseValue()
-
- self.skipws()
- self.expect(',', 'in "hwb"')
- self.skipws()
-
- b = self.parseValue()
-
- self.skipws()
- self.expect(')', 'at end of "hwb"')
-
- self.skipws()
- self.expectEnd()
-
- return HWB(h, w, b)
-
- def parseCMYK(self):
- self.expect('(', 'after "cmyk"')
- self.skipws()
-
- c = self.parseValue()
-
- self.skipws()
- self.expect(',', 'in "cmyk"')
- self.skipws()
-
- m = self.parseValue()
-
- self.skipws()
- self.expect(',', 'in "cmyk"')
- self.skipws()
-
- y = self.parseValue()
-
- self.skipws()
- self.expect(',', 'in "cmyk"')
- self.skipws()
-
- k = self.parseValue()
-
- self.skipws()
- self.expect(')', 'at end of "cmyk"')
-
- self.skipws()
- self.expectEnd()
-
- return CMYK(c, m, y, k)
-
- def parseGray(self):
- self.expect('(', 'after "gray"')
- self.skipws()
-
- g = self.parseValue()
-
- self.skipws()
- self.expect(')', 'at end of "gray')
-
- self.skipws()
- self.expectEnd()
-
- return Gray(g)
-
- def parseValue(self):
- n = self.parseNumber()
- self.skipws()
- if self._string[self._pos] == '%':
- n = n / 100.0
- self.pos += 1
- return n
-
- def parseAngle(self):
- n = self.parseNumber()
- self.skipws()
- tok = self.getToken()
- if tok == 'rad':
- n = n * 180.0 / math.pi
- elif tok == 'grad' or tok == 'gon':
- n = n * 0.9
- elif tok != 'deg':
- raise ValueError('bad angle unit "%s"' % tok)
- return n
-
-_color_re = re.compile(r'\s*(#|rgb|hsl|hwb|cmyk|gray|grey|%s)'
- % '|'.join(_x11_colors.keys()))
-
-
-def isAColor(s):
- return _color_re.match(s)
-
-
-def parseColor(s):
- return ColorParser(s).parseColor()
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/core.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/core.py
deleted file mode 100644
index d94a06c1f..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/core.py
+++ /dev/null
@@ -1,655 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import os
-import pkg_resources
-import platform
-import re
-import shutil
-import stat
-import subprocess
-import sys
-import tempfile
-import tokenize
-import json
-import time
-
-try:
- {}.iteritems
- iteritems = lambda x: x.iteritems()
- iterkeys = lambda x: x.iterkeys()
-except AttributeError:
- iteritems = lambda x: x.items()
- iterkeys = lambda x: x.keys()
-try:
- unicode
-except NameError:
- unicode = str
-
-if sys.version_info < (3, 4):
- import biplist
- def plist_from_bytes(data):
- return biplist.readPlistFromString(data)
- def plist_bytes(data):
- return biplist.Data(data)
-else:
- import plistlib
- def plist_from_bytes(data):
- return plistlib.loads(data)
- def plist_bytes(data):
- return data
-
-from mac_alias import *
-from ds_store import *
-
-from . import colors
-from . import licensing
-
-try:
- from . import badge
-except ImportError:
- badge = None
-
-_hexcolor_re = re.compile(r'#[0-9a-f]{3}(?:[0-9a-f]{3})?')
-
-# The first element in the platform.mac_ver() tuple is a string containing the
-# macOS version (e.g., '10.15.6'). Parse into an integer tuple.
-MACOS_VERSION = tuple(int(v) for v in platform.mac_ver()[0].split('.'))
-
-class DMGError(Exception):
- pass
-
-def hdiutil(cmd, *args, **kwargs):
- plist = kwargs.get('plist', True)
- all_args = ['/usr/bin/hdiutil', cmd]
- all_args.extend(args)
- if plist:
- all_args.append('-plist')
- p = subprocess.Popen(all_args, stdout=subprocess.PIPE, close_fds=True)
- output, errors = p.communicate()
- if plist:
- results = plist_from_bytes(output)
- else:
- results = output
- retcode = p.wait()
- return retcode, results
-
-# On Python 2 we can just execfile() it, but Python 3 deprecated that
-def load_settings(filename, settings):
- if sys.version_info[0] == 2:
- execfile(filename, settings, settings)
- else:
- encoding = 'utf-8'
- with open(filename, 'rb') as fp:
- try:
- encoding = tokenize.detect_encoding(fp.readline)[0]
- except SyntaxError:
- pass
-
- with open(filename, 'r', encoding=encoding) as fp:
- exec(compile(fp.read(), filename, 'exec'), settings, settings)
-
-def load_json(filename, settings):
- """Read an appdmg .json spec. Uses the defaults for appdmg, rather than
- the usual defaults for dmgbuild. """
-
- with open(filename, 'r') as fp:
- json_data = json.load(fp)
-
- if 'title' not in json_data:
- raise ValueError('missing \'title\' in JSON settings file')
- if 'contents' not in json_data:
- raise ValueError('missing \'contents\' in JSON settings file')
-
- settings['volume_name'] = json_data['title']
- settings['icon'] = json_data.get('icon', None)
- settings['badge_icon'] = json_data.get('badge-icon', None)
- bk = json_data.get('background', None)
- if bk is None:
- bk = json_data.get('background-color', None)
- if bk is not None:
- settings['background'] = bk
- settings['icon_size'] = json_data.get('icon-size', 80)
- wnd = json_data.get('window', { 'position': (100, 100),
- 'size': (640, 480) })
- pos = wnd.get('position', { 'x': 100, 'y': 100 })
- siz = wnd.get('size', { 'width': 640, 'height': 480 })
- settings['window_rect'] = ((pos.get('x', 100), pos.get('y', 100)),
- (siz.get('width', 640), siz.get('height', 480)))
- settings['format'] = json_data.get('format', 'UDZO')
- settings['compression_level'] = json_data.get('compression-level', None)
- settings['license'] = json_data.get('license', None)
- files = []
- hide = []
- hide_extensions = []
- symlinks = {}
- icon_locations = {}
- for fileinfo in json_data.get('contents', []):
- if 'path' not in fileinfo:
- raise ValueError('missing \'path\' in contents in JSON settings file')
- if 'x' not in fileinfo:
- raise ValueError('missing \'x\' in contents in JSON settings file')
- if 'y' not in fileinfo:
- raise ValueError('missing \'y\' in contents in JSON settings file')
-
- kind = fileinfo.get('type', 'file')
- path = fileinfo['path']
- name = fileinfo.get('name', os.path.basename(path.rstrip('/')))
- if kind == 'file':
- files.append((path, name))
- elif kind == 'link':
- symlinks[name] = path
- elif kind == 'position':
- pass
- icon_locations[name] = (fileinfo['x'], fileinfo['y'])
- hide_ext = fileinfo.get('hide_extension', False)
- if hide_ext:
- hide_extensions.append(name)
- hidden = fileinfo.get('hidden', False)
- if hidden:
- hide.append(name)
-
- settings['files'] = files
- settings['hide_extensions'] = hide_extensions
- settings['hide'] = hide
- settings['symlinks'] = symlinks
- settings['icon_locations'] = icon_locations
-
-def build_dmg(filename, volume_name, settings_file=None, settings={},
- defines={}, lookForHiDPI=True, detach_retries=5):
- options = {
- # Default settings
- 'filename': filename,
- 'volume_name': volume_name,
- 'format': 'UDBZ',
- 'compression_level': None,
- 'size': None,
- 'files': [],
- 'symlinks': {},
- 'hide': [],
- 'hide_extensions': [],
- 'icon': None,
- 'badge_icon': None,
- 'background': None,
- 'show_status_bar': False,
- 'show_tab_view': False,
- 'show_toolbar': False,
- 'show_pathbar': False,
- 'show_sidebar': False,
- 'sidebar_width': 180,
- 'arrange_by': None,
- 'grid_offset': (0, 0),
- 'grid_spacing': 100.0,
- 'scroll_position': (0.0, 0.0),
- 'show_icon_preview': False,
- 'show_item_info': False,
- 'label_pos': 'bottom',
- 'text_size': 16.0,
- 'icon_size': 128.0,
- 'include_icon_view_settings': 'auto',
- 'include_list_view_settings': 'auto',
- 'list_icon_size': 16.0,
- 'list_text_size': 12.0,
- 'list_scroll_position': (0, 0),
- 'list_sort_by': 'name',
- 'list_use_relative_dates': True,
- 'list_calculate_all_sizes': False,
- 'list_columns': ('name', 'date-modified', 'size', 'kind', 'date-added'),
- 'list_column_widths': {
- 'name': 300,
- 'date-modified': 181,
- 'date-created': 181,
- 'date-added': 181,
- 'date-last-opened': 181,
- 'size': 97,
- 'kind': 115,
- 'label': 100,
- 'version': 75,
- 'comments': 300,
- },
- 'list_column_sort_directions': {
- 'name': 'ascending',
- 'date-modified': 'descending',
- 'date-created': 'descending',
- 'date-added': 'descending',
- 'date-last-opened': 'descending',
- 'size': 'descending',
- 'kind': 'ascending',
- 'label': 'ascending',
- 'version': 'ascending',
- 'comments': 'ascending',
- },
- 'window_rect': ((100, 100), (640, 280)),
- 'default_view': 'icon-view',
- 'icon_locations': {},
- 'license': None,
- 'defines': defines
- }
-
- # Execute the settings file
- if settings_file:
- # We now support JSON settings files using appdmg's format
- if settings_file.endswith('.json'):
- load_json(settings_file, options)
- else:
- load_settings(settings_file, options)
-
- # Add any overrides
- options.update(settings)
-
- # Set up the finder data
- bounds = options['window_rect']
-
- bounds_string = '{{%s, %s}, {%s, %s}}' % (bounds[0][0],
- bounds[0][1],
- bounds[1][0],
- bounds[1][1])
- bwsp = {
- 'ShowStatusBar': options['show_status_bar'],
- 'WindowBounds': bounds_string,
- 'ContainerShowSidebar': False,
- 'PreviewPaneVisibility': False,
- 'SidebarWidth': options['sidebar_width'],
- 'ShowTabView': options['show_tab_view'],
- 'ShowToolbar': options['show_toolbar'],
- 'ShowPathbar': options['show_pathbar'],
- 'ShowSidebar': options['show_sidebar']
- }
-
- arrange_options = {
- 'name': 'name',
- 'date-modified': 'dateModified',
- 'date-created': 'dateCreated',
- 'date-added': 'dateAdded',
- 'date-last-opened': 'dateLastOpened',
- 'size': 'size',
- 'kind': 'kind',
- 'label': 'label',
- }
-
- icvp = {
- 'viewOptionsVersion': 1,
- 'backgroundType': 0,
- 'backgroundColorRed': 1.0,
- 'backgroundColorGreen': 1.0,
- 'backgroundColorBlue': 1.0,
- 'gridOffsetX': float(options['grid_offset'][0]),
- 'gridOffsetY': float(options['grid_offset'][1]),
- 'gridSpacing': float(options['grid_spacing']),
- 'arrangeBy': str(arrange_options.get(options['arrange_by'], 'none')),
- 'showIconPreview': options['show_icon_preview'] == True,
- 'showItemInfo': options['show_item_info'] == True,
- 'labelOnBottom': options['label_pos'] == 'bottom',
- 'textSize': float(options['text_size']),
- 'iconSize': float(options['icon_size']),
- 'scrollPositionX': float(options['scroll_position'][0]),
- 'scrollPositionY': float(options['scroll_position'][1])
- }
-
- background = options['background']
-
- columns = {
- 'name': 'name',
- 'date-modified': 'dateModified',
- 'date-created': 'dateCreated',
- 'date-added': 'dateAdded',
- 'date-last-opened': 'dateLastOpened',
- 'size': 'size',
- 'kind': 'kind',
- 'label': 'label',
- 'version': 'version',
- 'comments': 'comments'
- }
-
- default_widths = {
- 'name': 300,
- 'date-modified': 181,
- 'date-created': 181,
- 'date-added': 181,
- 'date-last-opened': 181,
- 'size': 97,
- 'kind': 115,
- 'label': 100,
- 'version': 75,
- 'comments': 300,
- }
-
- default_sort_directions = {
- 'name': 'ascending',
- 'date-modified': 'descending',
- 'date-created': 'descending',
- 'date-added': 'descending',
- 'date-last-opened': 'descending',
- 'size': 'descending',
- 'kind': 'ascending',
- 'label': 'ascending',
- 'version': 'ascending',
- 'comments': 'ascending',
- }
-
- lsvp = {
- 'viewOptionsVersion': 1,
- 'sortColumn': columns.get(options['list_sort_by'], 'name'),
- 'textSize': float(options['list_text_size']),
- 'iconSize': float(options['list_icon_size']),
- 'showIconPreview': options['show_icon_preview'],
- 'scrollPositionX': options['list_scroll_position'][0],
- 'scrollPositionY': options['list_scroll_position'][1],
- 'useRelativeDates': options['list_use_relative_dates'],
- 'calculateAllSizes': options['list_calculate_all_sizes'],
- }
-
- lsvp['columns'] = {}
- cndx = {}
-
- for n, column in enumerate(options['list_columns']):
- cndx[column] = n
- width = options['list_column_widths'].get(column,
- default_widths[column])
- asc = 'ascending' == options['list_column_sort_directions'].get(column,
- default_sort_directions[column])
-
- lsvp['columns'][columns[column]] = {
- 'index': n,
- 'width': width,
- 'identifier': columns[column],
- 'visible': True,
- 'ascending': asc
- }
-
- n = len(options['list_columns'])
- for k in iterkeys(columns):
- if cndx.get(k, None) is None:
- cndx[k] = n
- width = default_widths[k]
- asc = 'ascending' == default_sort_directions[k]
-
- lsvp['columns'][columns[column]] = {
- 'index': n,
- 'width': width,
- 'identifier': columns[column],
- 'visible': False,
- 'ascending': asc
- }
-
- n += 1
-
- default_view = options['default_view']
- views = {
- 'icon-view': b'icnv',
- 'column-view': b'clmv',
- 'list-view': b'Nlsv',
- 'coverflow': b'Flwv'
- }
-
- icvl = (b'type', views.get(default_view, 'icnv'))
-
- include_icon_view_settings = default_view == 'icon-view' \
- or options['include_icon_view_settings'] not in \
- ('auto', 'no', 0, False, None)
- include_list_view_settings = default_view in ('list-view', 'coverflow') \
- or options['include_list_view_settings'] not in \
- ('auto', 'no', 0, False, None)
-
- filename = options['filename']
- volume_name = options['volume_name']
-
- # Construct a writeable image to start with
- dirname, basename = os.path.split(os.path.realpath(filename))
- if not basename.endswith('.dmg'):
- basename += '.dmg'
- writableFile = tempfile.NamedTemporaryFile(dir=dirname, prefix='.temp',
- suffix=basename)
-
- total_size = options['size']
- if total_size == None:
- # Start with a size of 128MB - this way we don't need to calculate the
- # size of the background image, volume icon, and .DS_Store file (and
- # 128 MB should be well sufficient for even the most outlandish image
- # sizes, like an uncompressed 5K multi-resolution TIFF)
- total_size = 128 * 1024 * 1024
-
- def roundup(x, n):
- return x if x % n == 0 else x + n - x % n
-
- for path in options['files']:
- if isinstance(path, tuple):
- path = path[0]
-
- if not os.path.islink(path) and os.path.isdir(path):
- for dirpath, dirnames, filenames in os.walk(path):
- for f in filenames:
- fp = os.path.join(dirpath, f)
- total_size += roundup(os.lstat(fp).st_size, 4096)
- else:
- total_size += roundup(os.lstat(path).st_size, 4096)
-
- for name,target in iteritems(options['symlinks']):
- total_size += 4096
-
- total_size = str(max(total_size / 1000, 1024)) + 'K'
-
- ret, output = hdiutil('create',
- '-ov',
- '-volname', volume_name,
- '-fs', 'HFS+',
- '-fsargs', '-c c=64,a=16,e=16',
- '-size', total_size,
- writableFile.name)
-
- if ret:
- raise DMGError('Unable to create disk image')
-
- # IDME was deprecated in macOS 10.15/Catalina; as a result, use of -noidme
- # started raising a warning.
- if MACOS_VERSION >= (10, 15):
- ret, output = hdiutil('attach',
- '-nobrowse',
- '-owners', 'off',
- writableFile.name)
- else:
- ret, output = hdiutil('attach',
- '-nobrowse',
- '-owners', 'off',
- '-noidme',
- writableFile.name)
-
- if ret:
- raise DMGError('Unable to attach disk image')
-
- try:
- for info in output['system-entities']:
- if info.get('mount-point', None):
- device = info['dev-entry']
- mount_point = info['mount-point']
-
- icon = options['icon']
- if badge:
- badge_icon = options['badge_icon']
- else:
- badge_icon = None
- icon_target_path = os.path.join(mount_point, '.VolumeIcon.icns')
- if icon:
- shutil.copyfile(icon, icon_target_path)
- elif badge_icon:
- badge.badge_disk_icon(badge_icon, icon_target_path)
-
- if icon or badge_icon:
- subprocess.call(['/usr/bin/SetFile', '-a', 'C', mount_point])
-
- background_bmk = None
-
- if not isinstance(background, (str, unicode)):
- pass
- elif colors.isAColor(background):
- c = colors.parseColor(background).to_rgb()
-
- icvp['backgroundType'] = 1
- icvp['backgroundColorRed'] = float(c.r)
- icvp['backgroundColorGreen'] = float(c.g)
- icvp['backgroundColorBlue'] = float(c.b)
- else:
- if os.path.isfile(background):
- # look to see if there are HiDPI resources available
-
- if lookForHiDPI is True:
- name, extension = os.path.splitext(os.path.basename(background))
- orderedImages = [background]
- imageDirectory = os.path.dirname(background)
- if imageDirectory == '':
- imageDirectory = '.'
- for candidateName in os.listdir(imageDirectory):
- hasScale = re.match(
- r'^(?P<name>.+)@(?P<scale>\d+)x(?P<extension>\.\w+)$',
- candidateName)
- if hasScale and name == hasScale.group('name') and \
- extension == hasScale.group('extension'):
- scale = int(hasScale.group('scale'))
- if len(orderedImages) < scale:
- orderedImages += [None] * (scale - len(orderedImages))
- orderedImages[scale - 1] = os.path.join(imageDirectory, candidateName)
-
- if len(orderedImages) > 1:
- # compile the grouped tiff
- backgroundFile = tempfile.NamedTemporaryFile(suffix='.tiff')
- background = backgroundFile.name
- output = tempfile.TemporaryFile(mode='w+')
- try:
- subprocess.check_call(
- ['/usr/bin/tiffutil', '-cathidpicheck'] +
- list(filter(None, orderedImages)) +
- ['-out', background], stdout=output, stderr=output)
- except Exception as e:
- output.seek(0)
- raise ValueError(
- 'unable to compile combined HiDPI file "%s" got error: %s\noutput: %s'
- % (background, str(e), output.read()))
-
- _, kind = os.path.splitext(background)
- path_in_image = os.path.join(mount_point, '.background' + kind)
- shutil.copyfile(background, path_in_image)
- elif pkg_resources.resource_exists('dmgbuild', 'resources/' + background + '.tiff'):
- tiffdata = pkg_resources.resource_string(
- 'dmgbuild',
- 'resources/' + background + '.tiff')
- path_in_image = os.path.join(mount_point, '.background.tiff')
-
- with open(path_in_image, 'wb') as f:
- f.write(tiffdata)
- else:
- raise ValueError('background file "%s" not found' % background)
-
- alias = Alias.for_file(path_in_image)
- background_bmk = Bookmark.for_file(path_in_image)
-
- icvp['backgroundType'] = 2
- icvp['backgroundImageAlias'] = plist_bytes(alias.to_bytes())
-
- for f in options['files']:
- if isinstance(f, tuple):
- f_in_image = os.path.join(mount_point, f[1])
- f = f[0]
- else:
- basename = os.path.basename(f.rstrip('/'))
- f_in_image = os.path.join(mount_point, basename)
-
- # use system ditto command to preserve code signing, etc.
- subprocess.call(['/usr/bin/ditto', f, f_in_image])
-
- for name,target in iteritems(options['symlinks']):
- name_in_image = os.path.join(mount_point, name)
- os.symlink(target, name_in_image)
-
- to_hide = []
- for name in options['hide_extensions']:
- name_in_image = os.path.join(mount_point, name)
- to_hide.append(name_in_image)
-
- if to_hide:
- subprocess.call(['/usr/bin/SetFile', '-a', 'E'] + to_hide)
-
- to_hide = []
- for name in options['hide']:
- name_in_image = os.path.join(mount_point, name)
- to_hide.append(name_in_image)
-
- if to_hide:
- subprocess.call(['/usr/bin/SetFile', '-a', 'V'] + to_hide)
-
- userfn = options.get('create_hook', None)
- if callable(userfn):
- userfn(mount_point, options)
-
- image_dsstore = os.path.join(mount_point, '.DS_Store')
-
- with DSStore.open(image_dsstore, 'w+') as d:
- d['.']['vSrn'] = ('long', 1)
- d['.']['bwsp'] = bwsp
- if include_icon_view_settings:
- d['.']['icvp'] = icvp
- if background_bmk:
- d['.']['pBBk'] = background_bmk
- if include_list_view_settings:
- d['.']['lsvp'] = lsvp
- d['.']['icvl'] = icvl
-
- for k,v in iteritems(options['icon_locations']):
- d[k]['Iloc'] = v
-
- # Delete .Trashes, if it gets created
- shutil.rmtree(os.path.join(mount_point, '.Trashes'), True)
- except:
- # Always try to detach
- hdiutil('detach', '-force', device, plist=False)
- raise
-
- for tries in range(detach_retries):
- ret, output = hdiutil('detach', device, plist=False)
- if not ret:
- break
- time.sleep(1)
-
- if ret:
- hdiutil('detach', '-force', device, plist=False)
- raise DMGError('Unable to detach device cleanly')
-
- # Shrink the output to the minimum possible size
- ret, output = hdiutil('resize',
- '-quiet',
- '-sectors', 'min',
- writableFile.name,
- plist=False)
-
- if ret:
- raise DMGError('Unable to shrink')
-
- key_prefix = {'UDZO': 'zlib', 'UDBZ': 'bzip2', 'ULFO': 'lzfse'}
- compression_level = options['compression_level']
- if options['format'] in key_prefix and compression_level:
- compression_args = [
- '-imagekey',
- key_prefix[options['format']] + '-level=' + str(compression_level)
- ]
- else:
- compression_args = []
-
- ret, output = hdiutil('convert', writableFile.name,
- '-format', options['format'],
- '-ov',
- '-o', filename, *compression_args)
-
- if ret:
- raise DMGError('Unable to convert')
-
- if options['license']:
- ret, output = hdiutil('unflatten', '-quiet', filename, plist=False)
-
- if ret:
- raise DMGError('Unable to unflatten to add license')
-
- licensing.add_license(filename, options['license'])
-
- ret, output = hdiutil('flatten', '-quiet', filename, plist=False)
-
- if ret:
- raise DMGError('Unable to flatten after adding license')
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/licensing.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/licensing.py
deleted file mode 100644
index 871104040..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/licensing.py
+++ /dev/null
@@ -1,461 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import os
-import struct
-
-from .resources import *
-
-# ISO language and country codes to Macintosh Region codes (from Script.h)
-# <key> == CFLocaleCreateCanonicalLocaleIdentifierFromScriptManagerCodes(NULL,
-# kTextLanguageDontCare,
-# <value>)
-region_codes = {
- "en_US": 0,
- "fr_FR": 1,
- "en_GB": 2,
- "de_DE": 3,
- "it_IT": 4,
- "nl_NL": 5,
- "nl_BE": 6,
- "sv_SE": 7,
- "es_ES": 8,
- "da_DK": 9,
- "pt_PT": 10,
- "fr_CA": 11,
- "nb_NO": 12,
- "he_IL": 13,
- "ja_JP": 14,
- "en_AU": 15,
- "ar": 16,
- "fi_FI": 17,
- "fr_CH": 18,
- "de_CH": 19,
- "el_GR": 20,
- "is_IS": 21,
- "mt_MT": 22,
- "el_CY": 23,
- "tr_TR": 24,
- "hi_IN": 33,
- "ur_PK": 34,
- "it_CH": 36,
- "ro_RO": 39,
- "grc": 40,
- "lt_LT": 41,
- "pl_PL": 42,
- "hu_HU": 43,
- "et_EE": 44,
- "lv_LV": 45,
- "se": 46,
- "fo_FO": 47,
- "fa_IR": 48,
- "ru_RU": 49,
- "ga_IE": 50,
- "ko_KR": 51,
- "zh_CN": 52,
- "zh_TW": 53,
- "th_TH": 54,
- "cs_CZ": 56,
- "sk_SK": 57,
- "bn": 60,
- "be_BY": 61,
- "uk_UA": 62,
- "sr_RS": 65,
- "sl_SI": 66,
- "mk_MK": 67,
- "hr_HR": 68,
- "pt_BR": 71,
- "bg_BG": 72,
- "ca_ES": 73,
- "gd": 75,
- "gv": 76,
- "br": 77,
- "iu_CA": 78,
- "cy": 79,
- "ga-Latg_IE": 81,
- "en_CA": 82,
- "dz_BT": 83,
- "hy_AM": 84,
- "ka_GE": 85,
- "es_419": 86,
- "to_TO": 88,
- "fr_001": 91,
- "de_AT": 92,
- "gu_IN": 94,
- "pa": 95,
- "ur_IN": 96,
- "vi_VN": 97,
- "fr_BE": 98,
- "uz_UZ": 99,
- "en_SG": 100,
- "nn_NO": 101,
- "af_ZA": 102,
- "eo": 103,
- "mr_IN": 104,
- "bo": 105,
- "ne_NP": 106,
- "kl": 107,
- "en_IE": 108
-}
-
-# Map of region constants to script constants (from Script.h)
-# TextEncoding textEncoding;
-# GetTextEncodingFromScriptInfo(kTextScriptDontCare, kTextLanguageDontCare, <key>, &textEncoding);
-# <value> == GetTextEncodingBase(textEncoding);
-script_codes = {
- 0: 0,
- 1: 0,
- 2: 0,
- 3: 0,
- 4: 0,
- 5: 0,
- 6: 0,
- 7: 0,
- 8: 0,
- 9: 0,
- 10: 0,
- 11: 0,
- 12: 0,
- 13: 5,
- 14: 1,
- 15: 0,
- 16: 4,
- 17: 0,
- 18: 0,
- 19: 0,
- 20: 6,
- 21: 37,
- 22: 0,
- 23: 6,
- 24: 35,
- 25: 36,
- 26: 0,
- 27: 0,
- 30: 0,
- 31: 0,
- 32: 0,
- 33: 9,
- 34: 4,
- 35: 35,
- 36: 0,
- 37: 0,
- 39: 38,
- 40: 6,
- 41: 29,
- 42: 29,
- 43: 29,
- 44: 29,
- 45: 29,
- 46: 0,
- 47: 37,
- 48: 140,
- 49: 7,
- 50: 39,
- 51: 3,
- 52: 25,
- 53: 2,
- 54: 21,
- 56: 29,
- 57: 29,
- 59: 29,
- 60: 13,
- 61: 7,
- 62: 7,
- 64: 6,
- 65: 7,
- 66: 36,
- 67: 7,
- 68: 36,
- 70: 0,
- 71: 0,
- 72: 7,
- 73: 0,
- 75: 39,
- 76: 39,
- 77: 39,
- 78: 236,
- 79: 39,
- 81: 40,
- 82: 0,
- 83: 26,
- 84: 24,
- 85: 23,
- 86: 0,
- 88: 0,
- 91: 0,
- 92: 0,
- 94: 11,
- 95: 10,
- 96: 4,
- 97: 30,
- 98: 0,
- 99: 7,
- 100: 0,
- 101: 0,
- 102: 0,
- 103: 0,
- 104: 9,
- 105: 26,
- 106: 9,
- 107: 0,
- 108: 0
-}
-
-# Map of TextEncodingBase constants to Python encoder names (from TextCommon.h)
-encodings_map = {
- 0: 'mac_roman', # kTextEncodingMacRoman
- 1: 'shift_jis', # kTextEncodingMacJapanese
- 2: 'big5', # kTextEncodingMacChineseTrad
- 3: 'euc_kr', # kTextEncodingMacKorean
- 4: 'mac_arabic', # kTextEncodingMacArabic
- 6: 'mac_greek', # kTextEncodingMacGreek
- 7: 'mac_cyrillic', # kTextEncodingMacCyrillic
- 21: 'iso8859_11', # kTextEncodingMacThai
- 25: 'euc-cn', # kTextEncodingMacChineseSimp
- 29: 'mac_centeuro', # kTextEncodingMacCentralEurRoman
- 35: 'mac_turkish', # kTextEncodingMacTurkish
- 36: 'mac_croatian', # kTextEncodingMacCroatian
- 37: 'mac_iceland', # kTextEncodingMacIcelandic
- 38: 'mac_romanian', # kTextEncodingMacRomanian
- 140: 'mac_farsi' # kTextEncodingMacFarsi
-}
-
-# Standard fonts
-fonts = {
- 'New York': 2,
- 'Geneva': 3,
- 'Monaco': 4,
- 'Venice': 5,
- 'London': 6,
- 'Athens': 7,
- 'San Francisco': 8,
- 'Toronto': 9,
- 'Cairo': 11,
- 'Los Angeles': 12,
- 'Times': 20,
- 'Helvetica': 21,
- 'Courier': 22,
- 'Symbol': 23,
- 'Mobile': 24
-}
-
-# Buttons (these come from the SLAResources file which you can find in the SLA
-# SDK on developer.apple.com)
-default_buttons = {
- 0: (
- b'English',
- b'Agree',
- b'Disagree',
- b'Print',
- b'Save',
- b'If you agree with the terms of this license, press "Agree" to '
- b'install the software. If you do not agree, press "Disagree".'
- ),
-
- 3: (
- b'Deutsch',
- b'Akzeptieren',
- b'Ablehnen',
- b'Drucken',
- b'Sichern...',
- b'Klicken Sie in \xd2Akzeptieren\xd3, wenn Sie mit den Bestimmungen des Software-Lizenzvertrags einverstanden sind. Falls nicht, bitte \xd2Ablehnen\xd3 anklicken. Sie k\x9annen die Software nur installieren, wenn Sie \xd2Akzeptieren\xd3 angeklickt haben.'
- ),
-
- 8: (
- b'Espa\x96ol',
- b'Aceptar',
- b'No aceptar',
- b'Imprimir',
- b'Guardar...',
- b'Si est\x87 de acuerdo con los t\x8erminos de esta licencia, pulse "Aceptar" para instalar el software. En el supuesto de que no est\x8e de acuerdo con los t\x8erminos de esta licencia, pulse "No aceptar."'
- ),
-
- 1: (
- b'Fran\x8dais',
- b'Accepter',
- b'Refuser',
- b'Imprimer',
- b'Enregistrer...',
- b'Si vous acceptez les termes de la pr\x8esente licence, cliquez sur "Accepter" afin d\'installer le logiciel. Si vous n\'\x90tes pas d\'accord avec les termes de la licence, cliquez sur "Refuser".'
- ),
-
- 4: (
- b'Italiano',
- b'Accetto',
- b'Rifiuto',
- b'Stampa',
- b'Registra...',
- b'Se accetti le condizioni di questa licenza, fai clic su "Accetto" per installare il software. Altrimenti fai clic su "Rifiuto".'
- ),
-
- 14: (
- b'Japanese',
- b'\x93\xaf\x88\xd3\x82\xb5\x82\xdc\x82\xb7',
- b'\x93\xaf\x88\xd3\x82\xb5\x82\xdc\x82\xb9\x82\xf1',
- b'\x88\xf3\x8d\xfc\x82\xb7\x82\xe9',
- b'\x95\xdb\x91\xb6...',
- b'\x96{\x83\\\x83t\x83g\x83E\x83G\x83A\x8eg\x97p\x8b\x96\x91\xf8\x8c_\x96\xf1\x82\xcc\x8f\xf0\x8c\x8f\x82\xc9\x93\xaf\x88\xd3\x82\xb3\x82\xea\x82\xe9\x8f\xea\x8d\x87\x82\xc9\x82\xcd\x81A\x83\\\x83t\x83g\x83E\x83G\x83A\x82\xf0\x83C\x83\x93\x83X\x83g\x81[\x83\x8b\x82\xb7\x82\xe9\x82\xbd\x82\xdf\x82\xc9\x81u\x93\xaf\x88\xd3\x82\xb5\x82\xdc\x82\xb7\x81v\x82\xf0\x89\x9f\x82\xb5\x82\xc4\x82\xad\x82\xbe\x82\xb3\x82\xa2\x81B\x81@\x93\xaf\x88\xd3\x82\xb3\x82\xea\x82\xc8\x82\xa2\x8f\xea\x8d\x87\x82\xc9\x82\xcd\x81A\x81u\x93\xaf\x88\xd3\x82\xb5\x82\xdc\x82\xb9\x82\xf1\x81v\x82\xf0\x89\x9f\x82\xb5\x82\xc4\x82\xad\x82\xbe\x82\xb3\x82\xa2\x81B'
- ),
-
- 5: (
- b'Nederlands',
- b'Ja',
- b'Nee',
- b'Print',
- b'Bewaar...',
- b'Indien u akkoord gaat met de voorwaarden van deze licentie, kunt u op \'Ja\' klikken om de programmatuur te installeren. Indien u niet akkoord gaat, klikt u op \'Nee\'.'
- ),
-
- 7: (
- b'Svensk',
- b'Godk\x8anns',
- b'Avb\x9ajs',
- b'Skriv ut',
- b'Spara...',
- b'Om Du godk\x8anner licensvillkoren klicka p\x8c "Godk\x8anns" f\x9ar att installera programprodukten. Om Du inte godk\x8anner licensvillkoren, klicka p\x8c "Avb\x9ajs".'
- ),
-
- 71: (
- b'Portugu\x90s',
- b'Concordar',
- b'Discordar',
- b'Imprimir',
- b'Salvar...',
- b'Se est\x87 de acordo com os termos desta licen\x8da, pressione "Concordar" para instalar o software. Se n\x8bo est\x87 de acordo, pressione "Discordar".'
- ),
-
- 52: (
- b'Simplified Chinese',
- b'\xcd\xac\xd2\xe2',
- b'\xb2\xbb\xcd\xac\xd2\xe2',
- b'\xb4\xf2\xd3\xa1',
- b'\xb4\xe6\xb4\xa2\xa1\xad',
- b'\xc8\xe7\xb9\xfb\xc4\xfa\xcd\xac\xd2\xe2\xb1\xbe\xd0\xed\xbf\xc9\xd0\xad\xd2\xe9\xb5\xc4\xcc\xf5\xbf\xee\xa3\xac\xc7\xeb\xb0\xb4\xa1\xb0\xcd\xac\xd2\xe2\xa1\xb1\xc0\xb4\xb0\xb2\xd7\xb0\xb4\xcb\xc8\xed\xbc\xfe\xa1\xa3\xc8\xe7\xb9\xfb\xc4\xfa\xb2\xbb\xcd\xac\xd2\xe2\xa3\xac\xc7\xeb\xb0\xb4\xa1\xb0\xb2\xbb\xcd\xac\xd2\xe2\xa1\xb1\xa1\xa3'
- ),
-
- 53: (
- b'Traditional Chinese',
- b'\xa6P\xb7N',
- b'\xa4\xa3\xa6P\xb7N',
- b'\xa6C\xa6L',
- b'\xc0x\xa6s\xa1K',
- b'\xa6p\xaaG\xb1z\xa6P\xb7N\xa5\xbb\xb3\\\xa5i\xc3\xd2\xb8\xcc\xaa\xba\xb1\xf8\xb4\xda\xa1A\xbd\xd0\xab\xf6\xa1\xa7\xa6P\xb7N\xa1\xa8\xa5H\xa6w\xb8\xcb\xb3n\xc5\xe9\xa1C\xa6p\xaaG\xa4\xa3\xa6P\xb7N\xa1A\xbd\xd0\xab\xf6\xa1\xa7\xa4\xa3\xa6P\xb7N\xa1\xa8\xa1C'
- ),
-
- 9: (
- b'Dansk',
- b'Enig',
- b'Uenig',
- b'Udskriv',
- b'Arkiver...',
- b'Hvis du accepterer betingelserne i licensaftalen, skal du klikke p\x8c \xd2Enig\xd3 for at installere softwaren. Klik p\x8c \xd2Uenig\xd3 for at annullere installeringen.'
- ),
-
- 17: (
- b'Suomi',
- b'Hyv\x8aksyn',
- b'En hyv\x8aksy',
- b'Tulosta',
- b'Tallenna\xc9',
- b'Hyv\x8aksy lisenssisopimuksen ehdot osoittamalla \xd5Hyv\x8aksy\xd5. Jos et hyv\x8aksy sopimuksen ehtoja, osoita \xd5En hyv\x8aksy\xd5.'
- ),
-
- 51: (
- b'Korean',
- b'\xb5\xbf\xc0\xc7',
- b'\xb5\xbf\xc0\xc7 \xbe\xc8\xc7\xd4',
- b'\xc7\xc1\xb8\xb0\xc6\xae',
- b'\xc0\xfa\xc0\xe5...',
- b'\xbb\xe7\xbf\xeb \xb0\xe8\xbe\xe0\xbc\xad\xc0\xc7 \xb3\xbb\xbf\xeb\xbf\xa1 \xb5\xbf\xc0\xc7\xc7\xcf\xb8\xe9, "\xb5\xbf\xc0\xc7" \xb4\xdc\xc3\xdf\xb8\xa6 \xb4\xad\xb7\xaf \xbc\xd2\xc7\xc1\xc6\xae\xbf\xfe\xbe\xee\xb8\xa6 \xbc\xb3\xc4\xa1\xc7\xcf\xbd\xca\xbd\xc3\xbf\xc0. \xb5\xbf\xc0\xc7\xc7\xcf\xc1\xf6 \xbe\xca\xb4\xc2\xb4\xd9\xb8\xe9, "\xb5\xbf\xc0\xc7 \xbe\xc8\xc7\xd4" \xb4\xdc\xc3\xdf\xb8\xa6 \xb4\xa9\xb8\xa3\xbd\xca\xbd\xc3\xbf\xc0.'
- ),
-
- 12: (
- b'Norsk',
- b'Enig',
- b'Ikke enig',
- b'Skriv ut',
- b'Arkiver...',
- b'Hvis De er enig i bestemmelsene i denne lisensavtalen, klikker De p\x8c "Enig"-knappen for \x8c installere programvaren. Hvis De ikke er enig, klikker De p\x8c "Ikke enig".'
- ),
-}
-
-class LPicResource (Resource):
- def __init__(self, res_id, res_name, default_lang, lpic, res_attrs=0):
- data = []
- data.append(struct.pack(b'>HH', default_lang, len(lpic)))
- for lang,rid,two_byte in lpic:
- data.append(struct.pack(b'>HHH', lang, rid, int(two_byte)))
- super(LPicResource, self).__init__(b'LPic', res_id, res_name,
- b''.join(data), res_attrs)
-
-def get_encoder_name(locale):
- if locale not in region_codes:
- raise Exception("Cannot determine region code for locale '%s'" % locale)
- region_code = region_codes[locale]
-
- if region_code not in script_codes:
- raise Exception("Cannot determine script code for locale '%s'" % locale)
- script_code = script_codes[region_code]
-
- if script_code not in encodings_map:
- raise Exception("Cannot determine Python encoder name for locale '%s' - "
- "encode the string data manually as a byte array instead" % locale)
- return encodings_map[script_code]
-
-def maybe_encode(s, encoding):
- if isinstance(s, bytes):
- return s
- return s.encode(encoding)
-
-def add_license(filename, license_info):
- """Add a license agreement to the specified disk image file, which should
- have been unflattened first."""
-
- fork = ResourceFork.from_file(filename)
-
- default_lang = license_info.get('default-language', 'en_US')
- default_lang_id = region_codes.get(default_lang, 0)
-
- lpic = []
- ndx = 1
- for language,license_data in license_info['licenses'].items():
- if language not in region_codes:
- raise Exception("Unknown language '" + language + "'. Valid languages are: " +
- ", ".join(sorted(region_codes.keys())))
- encoding_name = get_encoder_name(language)
- lang_id = region_codes[language]
-
- is_two_byte = lang_id in (14, 51, 52, 53) # Japanese, Korean, SimpChinese, TradChinese
-
- if os.path.isfile(license_data):
- mode = 'rb' if license_data.endswith('.rtf') else 'r'
- with open(license_data, mode=mode) as f:
- license_data = f.read()
-
- if type(license_data) == bytes and license_data.startswith(b'{\\rtf1'):
- fork.add(Resource(b'RTF ', 5000 + ndx, (language + ' SLA').encode(),
- license_data))
- else:
- fork.add(TextResource(5000 + ndx, (language + ' SLA').encode(),
- maybe_encode(license_data, encoding_name)))
- fork.add(StyleResource(5000 + ndx, (language + ' SLA').encode(),
- [Style(0, 12, 9, Style.Helvetica,
- 0, 0, (0, 0, 0))]))
-
- buttons = license_info.get('buttons', {}).get(language, None)
- if buttons is None:
- buttons = default_buttons.get(lang_id, None)
- if buttons is None:
- buttons = default_buttons[0]
-
- buttons = [maybe_encode(b, encoding_name) for b in buttons]
-
- fork.add(StringListResource(5000 + ndx, (language + ' Buttons').encode(), buttons))
-
- lpic.append((lang_id, ndx, is_two_byte))
-
- ndx += 1
-
- fork.add(LPicResource(5000, None, default_lang_id, lpic))
-
- fork.write_to_file(filename)
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/resources.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/resources.py
deleted file mode 100644
index 03e2a43f3..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/resources.py
+++ /dev/null
@@ -1,359 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-import struct
-
-class Resource (object):
- def __init__(self, res_type, res_id, res_name, data=None, res_attrs=0):
- self.res_type = res_type
- self.res_id = res_id
- self.res_name = res_name
- self.res_attrs = res_attrs
- if data is None:
- self.data = None
- self.data = data
-
- self.data_offset = None
- self.name_offset = None
-
- def __repr__(self):
- return 'Resource(%r, %r, %r, data=%r, res_attrs=%r)' % (self.res_type,
- self.res_id,
- self.res_name,
- self.data,
- self.res_attrs)
-
-class TMPLResource (Resource):
- def __init__(self, res_id, res_name, tmpl, res_attrs=0):
- data = []
- for name,typecode in tmpl:
- data.append(struct.pack(b'B', len(name)))
- data.append(name)
- data.append(typecode)
- super(TMPLResource, self).__init__(b'TMPL', res_id, res_name,
- b''.join(data), res_attrs)
-
-class StringListResource (Resource):
- def __init__(self, res_id, res_name, strings, res_attrs=0):
- data = []
- data.append(struct.pack(b'>H', len(strings)))
- for s in strings:
- data.append(struct.pack(b'B', len(s)))
- data.append(s)
-
- super(StringListResource, self).__init__(b'STR#', res_id, res_name,
- b''.join(data), res_attrs)
-
-class TextResource (Resource):
- def __init__(self, res_id, res_name, string, res_attrs=0):
- super(TextResource, self).__init__(b'TEXT', res_id, res_name,
- string, res_attrs)
-
-class Style (object):
- # Fonts
- NewYork = 2
- Geneva = 3
- Monaco = 4
- Venice = 5
- London = 6
- Athens = 7
- SanFrancisco = 8
- Toronto = 9
- Cairo = 11
- LosAngeles = 12
- Times = 20
- Helvetica = 21
- Courier = 22
- Symbol = 23
- Mobile = 24
-
- # Styles
- Bold = 0x0100
- Italic = 0x0200
- Underline = 0x0400
- Outline = 0x0800
- Shadow = 0x1000
- Condense = 0x2000
- Expand = 0x4000
-
- def __init__(self, start_character, height, ascent, font_id, face,
- size, color):
- self.start_character = start_character
- self.height = height
- self.ascent = ascent
- self.font_id = font_id
- self.face = face
- self.size = size
- self.color = color
-
- def __repr__(self):
- styles = []
- if self.face & Style.Bold:
- styles.append('Style.Bold')
- if self.face & Style.Italic:
- styles.append('Style.Italic')
- if self.face & Style.Underline:
- styles.append('Style.Underline')
- if self.face & Style.Outline:
- styles.append('Style.Outline')
- if self.face & Style.Shadow:
- styles.append('Style.Shadow')
- if self.face & Style.Condense:
- styles.append('Style.Condense')
- if self.face & Style.Expand:
- styles.append('Style.Expand')
- if self.face & ~0x4f00:
- styles.append('%#06x' % (self.face & ~0x4f00))
- if styles:
- styles = '|'.join(styles)
- else:
- styles = '0'
-
- font_revmap = {
- 2: 'Style.NewYork',
- 3: 'Style.Geneva',
- 4: 'Style.Monaco',
- 5: 'Style.Venice',
- 6: 'Style.London',
- 7: 'Style.Athens',
- 8: 'Style.SanFrancisco',
- 9: 'Style.Toronto',
- 11: 'Style.Cairo',
- 12: 'Style.LosAngeles',
- 20: 'Style.Times',
- 21: 'Style.Helvetica',
- 22: 'Style.Courier',
- 23: 'Style.Symbol',
- 24: 'Style.Mobile'
- }
-
- font = font_revmap.get(self.font_id, '%s' % self.font_id)
-
- return 'Style(%r, %r, %r, %s, %s, %r, %r)' % (
- self.start_character,
- self.height,
- self.ascent,
- font,
- styles,
- self.size,
- self.color)
-
-class StyleResource (Resource):
- def __init__(self, res_id, res_name, styles, res_attrs=0):
- data = []
- data.append(struct.pack(b'>H', len(styles)))
- for style in styles:
- data.append(struct.pack(b'>LHHHHHHHH',
- style.start_character,
- style.height,
- style.ascent,
- style.font_id,
- style.face,
- style.size,
- style.color[0],
- style.color[1],
- style.color[2]))
- super(StyleResource, self).__init__(b'styl', res_id, res_name,
- b''.join(data), res_attrs)
-
-class ResourceFork (object):
- def __init__(self, resources=None):
- self.types = {}
- self.attrs = 0
- if resources is not None:
- for res in resources:
- self.add(res)
-
- @classmethod
- def from_data(clss, data):
- if len(data) < 16:
- raise ValueError('Bad resource data - data too short')
-
- # Read the header
- data_start, map_start, data_len, map_len = struct.unpack(b'>LLLL',
- data[0:16])
-
- if data_start + data_len > len(data):
- raise ValueError('Bad resource data - data out of range')
- if map_start + map_len > len(data):
- raise ValueError('Bad resource data - map out of range')
- if map_len < 30:
- raise ValueError('Bad resource data - map too short')
-
- # Read the map header
- fork_attrs, type_offset, name_offset, max_type_ndx \
- = struct.unpack(b'>HHHH', data[map_start + 22:map_start + 30])
- num_types = max_type_ndx + 1
-
- if type_offset + 8 * num_types > map_len:
- raise ValueError('Bad resource data - type data outside map')
-
- if name_offset > map_len:
- raise ValueError('Bad resource data - names outside map')
-
- type_offset += map_start
- name_offset += map_start
-
- result = ResourceFork()
-
- # Now read the type list
- for ntype in range(0, num_types):
- type_pos = 2 + type_offset + 8 * ntype
- res_type, max_item_ndx, ref_offset \
- = struct.unpack(b'>4sHH', data[type_pos:type_pos+8])
- num_items = max_item_ndx + 1
-
- result.types[res_type] = []
-
- ref_list_offset = type_offset + ref_offset
- if ref_list_offset + 12 * num_items > map_start + map_len:
- raise ValueError('Bad resource data - ref list outside map')
-
- for nitem in range(0, num_items):
- ref_elt = ref_list_offset + 12 * nitem
- res_id, res_name_offset, data_offset \
- = struct.unpack(b'>hHL', data[ref_elt:ref_elt+8])
-
- res_attrs = data_offset >> 24
- data_offset &= 0xffffff
-
- if data_offset >= data_len:
- raise ValueError('Bad resource data - item data out of range')
-
- data_offset += data_start
- res_len = struct.unpack(b'>L', data[data_offset:data_offset+4])[0]
- if data_offset + res_len >= data_start + data_len:
- raise ValueError('Bad resource data - item data too large')
-
- res_data = data[data_offset + 4:data_offset + res_len + 4]
-
- if res_name_offset == 0xffff:
- res_name = None
- else:
- res_name_offset += name_offset
- if res_name_offset >= map_start + map_len:
- raise ValueError('Bad resource data - name out of range')
- try:
- # python2
- res_name_len = struct.unpack(b'B', data[res_name_offset])[0]
- except:
- # python3
- res_name_len = data[res_name_offset]
-
- res_name = data[res_name_offset + 1:res_name_offset + res_name_len + 1]
-
- result.types[res_type].append(Resource(res_type, res_id,
- res_name,
- res_data, res_attrs))
-
- return result
-
- @classmethod
- def from_file(clss, filename):
- with open(filename + '/..namedfork/rsrc', 'rb') as f:
- data = f.read()
- return clss.from_data(data)
-
- def to_data(self):
- data = []
- data_len = 0
- names = []
- names_len = 0
- types_len = len(self.types) * 8
- types_data = []
- reflist_data = []
- reflist_len = 0
-
- for res_type, items in self.types.items():
- types_data.append(struct.pack(b'>4sHH',
- res_type,
- len(items) - 1,
- 2 + types_len + reflist_len))
- for item in items:
- data_offset = data_len
-
- if item.res_name is None:
- name_offset = 65535
- else:
- name_offset = names_len
- n = item.res_name
- names.append(struct.pack(b'B', len(n)) + n)
- names_len += 1 + len(n)
-
- if item.data is None:
- data_len += 4
- else:
- data_len += 4 + (len(item.data) + 3) & ~3
-
- reflist_len += 12
- reflist_data.append(struct.pack(b'>hHLL',
- item.res_id,
- name_offset,
- (item.res_attrs << 24) \
- | data_offset,
- 0))
-
- # Header
- data.append(struct.pack(b'>LLLL240s', 256, 256 + data_len, data_len,
- 30 + types_len + reflist_len + names_len,
- b''))
-
- # Resource data
- for res_type, items in self.types.items():
- for item in items:
- if item.data is None:
- dlen = 0
- else:
- dlen = len(item.data)
- plen = (dlen + 3) & ~3
- data.append(struct.pack(b'>L', dlen))
- if item.data is not None:
- data.append(item.data)
- if plen != dlen:
- data.append(b'\0' * (plen - dlen))
-
- # Resource map header
- data.append(struct.pack(b'>16sLHHHHH',
- b'', 0, 0,
- self.attrs, 28, 30 + types_len + reflist_len,
- len(self.types) - 1))
-
- # Type list
- data.append(b''.join(types_data))
-
- # Reference lists
- data.append(b''.join(reflist_data))
-
- # Name list
- data.append(b''.join(names))
-
- return b''.join(data)
-
- def write_to_file(self, filename):
- with open(filename + '/..namedfork/rsrc', 'wb') as f:
- f.write(self.to_data())
-
- def __len__(self):
- return len(self.types)
-
- def __getitem__(self, key):
- return self.types[key]
-
- def __iter__(self):
- for res_type, items in self.types.items():
- for item in items:
- yield item
-
- def __repr__(self):
- output = []
- for item in self:
- output.append(repr(item))
- return 'ResourceFork([%s])' % ', '.join(output)
-
- def add(self, res):
- if res.res_type in self.types:
- self.types[res.res_type].append(res)
- else:
- self.types[res.res_type] = [res]
-
- def remove(self, res):
- self.types[res.res_type].remove(res)
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/__init__.py b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/__init__.py
deleted file mode 100644
index a6b812104..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .store import DSStore, DSStoreEntry
-
-__all__ = ['DSStore', 'DSStoreEntry']
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/__init__.py b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/__init__.py
deleted file mode 100644
index 7eb314107..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from .alias import *
-from .bookmark import *
-
-__all__ = [ 'ALIAS_KIND_FILE', 'ALIAS_KIND_FOLDER',
- 'ALIAS_HFS_VOLUME_SIGNATURE',
- 'ALIAS_FIXED_DISK', 'ALIAS_NETWORK_DISK', 'ALIAS_400KB_FLOPPY_DISK',
- 'ALIAS_800KB_FLOPPY_DISK', 'ALIAS_1_44MB_FLOPPY_DISK',
- 'ALIAS_EJECTABLE_DISK',
- 'ALIAS_NO_CNID',
- 'kBookmarkPath', 'kBookmarkCNIDPath', 'kBookmarkFileProperties',
- 'kBookmarkFileName', 'kBookmarkFileID', 'kBookmarkFileCreationDate',
- 'kBookmarkTOCPath', 'kBookmarkVolumePath',
- 'kBookmarkVolumeURL', 'kBookmarkVolumeName', 'kBookmarkVolumeUUID',
- 'kBookmarkVolumeSize', 'kBookmarkVolumeCreationDate',
- 'kBookmarkVolumeProperties', 'kBookmarkContainingFolder',
- 'kBookmarkUserName', 'kBookmarkUID', 'kBookmarkWasFileReference',
- 'kBookmarkCreationOptions', 'kBookmarkURLLengths',
- 'kBookmarkSecurityExtension',
- 'AppleShareInfo',
- 'VolumeInfo',
- 'TargetInfo',
- 'Alias',
- 'Bookmark',
- 'Data',
- 'URL' ]
-
-
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/bookmark.py b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/bookmark.py
deleted file mode 100644
index 0de6b9404..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/bookmark.py
+++ /dev/null
@@ -1,665 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file implements the Apple "bookmark" format, which is the replacement
-# for the old-fashioned alias format. The details of this format were
-# reverse engineered; some things are still not entirely clear.
-#
-from __future__ import unicode_literals, print_function
-
-import struct
-import uuid
-import datetime
-import os
-import sys
-import pprint
-
-try:
- from urlparse import urljoin
-except ImportError:
- from urllib.parse import urljoin
-
-if sys.platform == 'darwin':
- from . import osx
-
-def iteritems(x):
- return x.iteritems()
-
-try:
- unicode
-except NameError:
- unicode = str
- long = int
- xrange = range
- def iteritems(x):
- return x.items()
-
-from .utils import *
-
-BMK_DATA_TYPE_MASK = 0xffffff00
-BMK_DATA_SUBTYPE_MASK = 0x000000ff
-
-BMK_STRING = 0x0100
-BMK_DATA = 0x0200
-BMK_NUMBER = 0x0300
-BMK_DATE = 0x0400
-BMK_BOOLEAN = 0x0500
-BMK_ARRAY = 0x0600
-BMK_DICT = 0x0700
-BMK_UUID = 0x0800
-BMK_URL = 0x0900
-BMK_NULL = 0x0a00
-
-BMK_ST_ZERO = 0x0000
-BMK_ST_ONE = 0x0001
-
-BMK_BOOLEAN_ST_FALSE = 0x0000
-BMK_BOOLEAN_ST_TRUE = 0x0001
-
-# Subtypes for BMK_NUMBER are really CFNumberType values
-kCFNumberSInt8Type = 1
-kCFNumberSInt16Type = 2
-kCFNumberSInt32Type = 3
-kCFNumberSInt64Type = 4
-kCFNumberFloat32Type = 5
-kCFNumberFloat64Type = 6
-kCFNumberCharType = 7
-kCFNumberShortType = 8
-kCFNumberIntType = 9
-kCFNumberLongType = 10
-kCFNumberLongLongType = 11
-kCFNumberFloatType = 12
-kCFNumberDoubleType = 13
-kCFNumberCFIndexType = 14
-kCFNumberNSIntegerType = 15
-kCFNumberCGFloatType = 16
-
-# Resource property flags (from CFURLPriv.h)
-kCFURLResourceIsRegularFile = 0x00000001
-kCFURLResourceIsDirectory = 0x00000002
-kCFURLResourceIsSymbolicLink = 0x00000004
-kCFURLResourceIsVolume = 0x00000008
-kCFURLResourceIsPackage = 0x00000010
-kCFURLResourceIsSystemImmutable = 0x00000020
-kCFURLResourceIsUserImmutable = 0x00000040
-kCFURLResourceIsHidden = 0x00000080
-kCFURLResourceHasHiddenExtension = 0x00000100
-kCFURLResourceIsApplication = 0x00000200
-kCFURLResourceIsCompressed = 0x00000400
-kCFURLResourceIsSystemCompressed = 0x00000400
-kCFURLCanSetHiddenExtension = 0x00000800
-kCFURLResourceIsReadable = 0x00001000
-kCFURLResourceIsWriteable = 0x00002000
-kCFURLResourceIsExecutable = 0x00004000
-kCFURLIsAliasFile = 0x00008000
-kCFURLIsMountTrigger = 0x00010000
-
-# Volume property flags (from CFURLPriv.h)
-kCFURLVolumeIsLocal = 0x1 #
-kCFURLVolumeIsAutomount = 0x2 #
-kCFURLVolumeDontBrowse = 0x4 #
-kCFURLVolumeIsReadOnly = 0x8 #
-kCFURLVolumeIsQuarantined = 0x10
-kCFURLVolumeIsEjectable = 0x20 #
-kCFURLVolumeIsRemovable = 0x40 #
-kCFURLVolumeIsInternal = 0x80 #
-kCFURLVolumeIsExternal = 0x100 #
-kCFURLVolumeIsDiskImage = 0x200 #
-kCFURLVolumeIsFileVault = 0x400
-kCFURLVolumeIsLocaliDiskMirror = 0x800
-kCFURLVolumeIsiPod = 0x1000 #
-kCFURLVolumeIsiDisk = 0x2000
-kCFURLVolumeIsCD = 0x4000
-kCFURLVolumeIsDVD = 0x8000
-kCFURLVolumeIsDeviceFileSystem = 0x10000
-kCFURLVolumeSupportsPersistentIDs = 0x100000000
-kCFURLVolumeSupportsSearchFS = 0x200000000
-kCFURLVolumeSupportsExchange = 0x400000000
-# reserved 0x800000000
-kCFURLVolumeSupportsSymbolicLinks = 0x1000000000
-kCFURLVolumeSupportsDenyModes = 0x2000000000
-kCFURLVolumeSupportsCopyFile = 0x4000000000
-kCFURLVolumeSupportsReadDirAttr = 0x8000000000
-kCFURLVolumeSupportsJournaling = 0x10000000000
-kCFURLVolumeSupportsRename = 0x20000000000
-kCFURLVolumeSupportsFastStatFS = 0x40000000000
-kCFURLVolumeSupportsCaseSensitiveNames = 0x80000000000
-kCFURLVolumeSupportsCasePreservedNames = 0x100000000000
-kCFURLVolumeSupportsFLock = 0x200000000000
-kCFURLVolumeHasNoRootDirectoryTimes = 0x400000000000
-kCFURLVolumeSupportsExtendedSecurity = 0x800000000000
-kCFURLVolumeSupports2TBFileSize = 0x1000000000000
-kCFURLVolumeSupportsHardLinks = 0x2000000000000
-kCFURLVolumeSupportsMandatoryByteRangeLocks = 0x4000000000000
-kCFURLVolumeSupportsPathFromID = 0x8000000000000
-# reserved 0x10000000000000
-kCFURLVolumeIsJournaling = 0x20000000000000
-kCFURLVolumeSupportsSparseFiles = 0x40000000000000
-kCFURLVolumeSupportsZeroRuns = 0x80000000000000
-kCFURLVolumeSupportsVolumeSizes = 0x100000000000000
-kCFURLVolumeSupportsRemoteEvents = 0x200000000000000
-kCFURLVolumeSupportsHiddenFiles = 0x400000000000000
-kCFURLVolumeSupportsDecmpFSCompression = 0x800000000000000
-kCFURLVolumeHas64BitObjectIDs = 0x1000000000000000
-kCFURLVolumePropertyFlagsAll = 0xffffffffffffffff
-
-BMK_URL_ST_ABSOLUTE = 0x0001
-BMK_URL_ST_RELATIVE = 0x0002
-
-# Bookmark keys
-# = 0x1003
-kBookmarkPath = 0x1004 # Array of path components
-kBookmarkCNIDPath = 0x1005 # Array of CNIDs
-kBookmarkFileProperties = 0x1010 # (CFURL rp flags,
- # CFURL rp flags asked for,
- # 8 bytes NULL)
-kBookmarkFileName = 0x1020
-kBookmarkFileID = 0x1030
-kBookmarkFileCreationDate = 0x1040
-# = 0x1054 # ?
-# = 0x1055 # ?
-# = 0x1056 # ?
-# = 0x1101 # ?
-# = 0x1102 # ?
-kBookmarkTOCPath = 0x2000 # A list of (TOC id, ?) pairs
-kBookmarkVolumePath = 0x2002
-kBookmarkVolumeURL = 0x2005
-kBookmarkVolumeName = 0x2010
-kBookmarkVolumeUUID = 0x2011 # Stored (perversely) as a string
-kBookmarkVolumeSize = 0x2012
-kBookmarkVolumeCreationDate = 0x2013
-kBookmarkVolumeProperties = 0x2020 # (CFURL vp flags,
- # CFURL vp flags asked for,
- # 8 bytes NULL)
-kBookmarkVolumeIsRoot = 0x2030 # True if volume is FS root
-kBookmarkVolumeBookmark = 0x2040 # Embedded bookmark for disk image (TOC id)
-kBookmarkVolumeMountPoint = 0x2050 # A URL
-# = 0x2070
-kBookmarkContainingFolder = 0xc001 # Index of containing folder in path
-kBookmarkUserName = 0xc011 # User that created bookmark
-kBookmarkUID = 0xc012 # UID that created bookmark
-kBookmarkWasFileReference = 0xd001 # True if the URL was a file reference
-kBookmarkCreationOptions = 0xd010
-kBookmarkURLLengths = 0xe003 # See below
-# = 0xf017 # Localized name?
-# = 0xf022
-kBookmarkSecurityExtension = 0xf080
-# = 0xf081
-
-# kBookmarkURLLengths is an array that is set if the URL encoded by the
-# bookmark had a base URL; in that case, each entry is the length of the
-# base URL in question. Thus a URL
-#
-# file:///foo/bar/baz blam/blat.html
-#
-# will result in [3, 2], while the URL
-#
-# file:///foo bar/baz blam blat.html
-#
-# would result in [1, 2, 1, 1]
-
-
-class Data (object):
- def __init__(self, bytedata=None):
- #: The bytes, stored as a byte string
- self.bytes = bytes(bytedata)
-
- def __repr__(self):
- return 'Data(%r)' % self.bytes
-
-class URL (object):
- def __init__(self, base, rel=None):
- if rel is not None:
- #: The base URL, if any (a :class:`URL`)
- self.base = base
- #: The rest of the URL (a string)
- self.relative = rel
- else:
- self.base = None
- self.relative = base
-
- @property
- def absolute(self):
- """Return an absolute URL."""
- if self.base is None:
- return self.relative
- else:
- base_abs = self.base.absolute
- return urljoin(self.base.absolute, self.relative)
-
- def __repr__(self):
- return 'URL(%r)' % self.absolute
-
-class Bookmark (object):
- def __init__(self, tocs=None):
- if tocs is None:
- #: The TOCs for this Bookmark
- self.tocs = []
- else:
- self.tocs = tocs
-
- @classmethod
- def _get_item(cls, data, hdrsize, offset):
- offset += hdrsize
- if offset > len(data) - 8:
- raise ValueError('Offset out of range')
-
- length,typecode = struct.unpack(b'<II', data[offset:offset+8])
-
- if len(data) - offset < 8 + length:
- raise ValueError('Data item truncated')
-
- databytes = data[offset+8:offset+8+length]
-
- dsubtype = typecode & BMK_DATA_SUBTYPE_MASK
- dtype = typecode & BMK_DATA_TYPE_MASK
-
- if dtype == BMK_STRING:
- return databytes.decode('utf-8')
- elif dtype == BMK_DATA:
- return Data(databytes)
- elif dtype == BMK_NUMBER:
- if dsubtype == kCFNumberSInt8Type:
- return ord(databytes[0])
- elif dsubtype == kCFNumberSInt16Type:
- return struct.unpack(b'<h', databytes)[0]
- elif dsubtype == kCFNumberSInt32Type:
- return struct.unpack(b'<i', databytes)[0]
- elif dsubtype == kCFNumberSInt64Type:
- return struct.unpack(b'<q', databytes)[0]
- elif dsubtype == kCFNumberFloat32Type:
- return struct.unpack(b'<f', databytes)[0]
- elif dsubtype == kCFNumberFloat64Type:
- return struct.unpack(b'<d', databytes)[0]
- elif dtype == BMK_DATE:
- # Yes, dates really are stored as *BIG-endian* doubles; everything
- # else is little-endian
- secs = datetime.timedelta(seconds=struct.unpack(b'>d', databytes)[0])
- return osx_epoch + secs
- elif dtype == BMK_BOOLEAN:
- if dsubtype == BMK_BOOLEAN_ST_TRUE:
- return True
- elif dsubtype == BMK_BOOLEAN_ST_FALSE:
- return False
- elif dtype == BMK_UUID:
- return uuid.UUID(bytes=databytes)
- elif dtype == BMK_URL:
- if dsubtype == BMK_URL_ST_ABSOLUTE:
- return URL(databytes.decode('utf-8'))
- elif dsubtype == BMK_URL_ST_RELATIVE:
- baseoff,reloff = struct.unpack(b'<II', databytes)
- base = cls._get_item(data, hdrsize, baseoff)
- rel = cls._get_item(data, hdrsize, reloff)
- return URL(base, rel)
- elif dtype == BMK_ARRAY:
- result = []
- for aoff in xrange(offset+8,offset+8+length,4):
- eltoff, = struct.unpack(b'<I', data[aoff:aoff+4])
- result.append(cls._get_item(data, hdrsize, eltoff))
- return result
- elif dtype == BMK_DICT:
- result = {}
- for eoff in xrange(offset+8,offset+8+length,8):
- keyoff,valoff = struct.unpack(b'<II', data[eoff:eoff+8])
- key = cls._get_item(data, hdrsize, keyoff)
- val = cls._get_item(data, hdrsize, valoff)
- result[key] = val
- return result
- elif dtype == BMK_NULL:
- return None
-
- print('Unknown data type %08x' % typecode)
- return (typecode, databytes)
-
- @classmethod
- def from_bytes(cls, data):
- """Create a :class:`Bookmark` given byte data."""
-
- if len(data) < 16:
- raise ValueError('Not a bookmark file (too short)')
-
- if isinstance(data, bytearray):
- data = bytes(data)
-
- magic,size,dummy,hdrsize = struct.unpack(b'<4sIII', data[0:16])
-
- if magic != b'book':
- raise ValueError('Not a bookmark file (bad magic) %r' % magic)
-
- if hdrsize < 16:
- raise ValueError('Not a bookmark file (header size too short)')
-
- if hdrsize > size:
- raise ValueError('Not a bookmark file (header size too large)')
-
- if size != len(data):
- raise ValueError('Not a bookmark file (truncated)')
-
- tocoffset, = struct.unpack(b'<I', data[hdrsize:hdrsize+4])
-
- tocs = []
-
- while tocoffset != 0:
- tocbase = hdrsize + tocoffset
- if tocoffset > size - hdrsize \
- or size - tocbase < 20:
- raise ValueError('TOC offset out of range')
-
- tocsize,tocmagic,tocid,nexttoc,toccount \
- = struct.unpack(b'<IIIII',
- data[tocbase:tocbase+20])
-
- if tocmagic != 0xfffffffe:
- break
-
- tocsize += 8
-
- if size - tocbase < tocsize:
- raise ValueError('TOC truncated')
-
- if tocsize < 12 * toccount:
- raise ValueError('TOC entries overrun TOC size')
-
- toc = {}
- for n in xrange(0,toccount):
- ebase = tocbase + 20 + 12 * n
- eid,eoffset,edummy = struct.unpack(b'<III',
- data[ebase:ebase+12])
-
- if eid & 0x80000000:
- eid = cls._get_item(data, hdrsize, eid & 0x7fffffff)
-
- toc[eid] = cls._get_item(data, hdrsize, eoffset)
-
- tocs.append((tocid, toc))
-
- tocoffset = nexttoc
-
- return cls(tocs)
-
- def __getitem__(self, key):
- for tid,toc in self.tocs:
- if key in toc:
- return toc[key]
- raise KeyError('Key not found')
-
- def __setitem__(self, key, value):
- if len(self.tocs) == 0:
- self.tocs = [(1, {})]
- self.tocs[0][1][key] = value
-
- def get(self, key, default=None):
- """Lookup the value for a given key, returning a default if not
- present."""
- for tid,toc in self.tocs:
- if key in toc:
- return toc[key]
- return default
-
- @classmethod
- def _encode_item(cls, item, offset):
- if item is True:
- result = struct.pack(b'<II', 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_TRUE)
- elif item is False:
- result = struct.pack(b'<II', 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_FALSE)
- elif isinstance(item, unicode):
- encoded = item.encode('utf-8')
- result = (struct.pack(b'<II', len(encoded), BMK_STRING | BMK_ST_ONE)
- + encoded)
- elif isinstance(item, bytes):
- result = (struct.pack(b'<II', len(item), BMK_STRING | BMK_ST_ONE)
- + item)
- elif isinstance(item, Data):
- result = (struct.pack(b'<II', len(item.bytes),
- BMK_DATA | BMK_ST_ONE)
- + bytes(item.bytes))
- elif isinstance(item, bytearray):
- result = (struct.pack(b'<II', len(item),
- BMK_DATA | BMK_ST_ONE)
- + bytes(item))
- elif isinstance(item, int) or isinstance(item, long):
- if item > -0x80000000 and item < 0x7fffffff:
- result = struct.pack(b'<IIi', 4,
- BMK_NUMBER | kCFNumberSInt32Type, item)
- else:
- result = struct.pack(b'<IIq', 8,
- BMK_NUMBER | kCFNumberSInt64Type, item)
- elif isinstance(item, float):
- result = struct.pack(b'<IId', 8,
- BMK_NUMBER | kCFNumberFloat64Type, item)
- elif isinstance(item, datetime.datetime):
- secs = item - osx_epoch
- result = struct.pack(b'<II', 8, BMK_DATE | BMK_ST_ZERO) \
- + struct.pack(b'>d', float(secs.total_seconds()))
- elif isinstance(item, uuid.UUID):
- result = struct.pack(b'<II', 16, BMK_UUID | BMK_ST_ONE) \
- + item.bytes
- elif isinstance(item, URL):
- if item.base:
- baseoff = offset + 16
- reloff, baseenc = cls._encode_item(item.base, baseoff)
- xoffset, relenc = cls._encode_item(item.relative, reloff)
- result = b''.join([
- struct.pack(b'<IIII', 8, BMK_URL | BMK_URL_ST_RELATIVE,
- baseoff, reloff),
- baseenc,
- relenc])
- else:
- encoded = item.relative.encode('utf-8')
- result = struct.pack(b'<II', len(encoded),
- BMK_URL | BMK_URL_ST_ABSOLUTE) + encoded
- elif isinstance(item, list):
- ioffset = offset + 8 + len(item) * 4
- result = [struct.pack(b'<II', len(item) * 4, BMK_ARRAY | BMK_ST_ONE)]
- enc = []
- for elt in item:
- result.append(struct.pack(b'<I', ioffset))
- ioffset, ienc = cls._encode_item(elt, ioffset)
- enc.append(ienc)
- result = b''.join(result + enc)
- elif isinstance(item, dict):
- ioffset = offset + 8 + len(item) * 8
- result = [struct.pack(b'<II', len(item) * 8, BMK_DICT | BMK_ST_ONE)]
- enc = []
- for k,v in iteritems(item):
- result.append(struct.pack(b'<I', ioffset))
- ioffset, ienc = cls._encode_item(k, ioffset)
- enc.append(ienc)
- result.append(struct.pack(b'<I', ioffset))
- ioffset, ienc = cls._encode_item(v, ioffset)
- enc.append(ienc)
- result = b''.join(result + enc)
- elif item is None:
- result = struct.pack(b'<II', 0, BMK_NULL | BMK_ST_ONE)
- else:
- raise ValueError('Unknown item type when encoding: %s' % item)
-
- offset += len(result)
-
- # Pad to a multiple of 4 bytes
- if offset & 3:
- extra = 4 - (offset & 3)
- result += b'\0' * extra
- offset += extra
-
- return (offset, result)
-
- def to_bytes(self):
- """Convert this :class:`Bookmark` to a byte representation."""
-
- result = []
- tocs = []
- offset = 4 # For the offset to the first TOC
-
- # Generate the data and build the TOCs
- for tid,toc in self.tocs:
- entries = []
-
- for k,v in iteritems(toc):
- if isinstance(k, (str, unicode)):
- noffset = offset
- voffset, enc = self._encode_item(k, offset)
- result.append(enc)
- offset, enc = self._encode_item(v, voffset)
- result.append(enc)
- entries.append((noffset | 0x80000000, voffset))
- else:
- entries.append((k, offset))
- offset, enc = self._encode_item(v, offset)
- result.append(enc)
-
- # TOC entries must be sorted - CoreServicesInternal does a
- # binary search to find data
- entries.sort()
-
- tocs.append((tid, b''.join([struct.pack(b'<III',k,o,0)
- for k,o in entries])))
-
- first_toc_offset = offset
-
- # Now generate the TOC headers
- for ndx,toc in enumerate(tocs):
- tid, data = toc
- if ndx == len(tocs) - 1:
- next_offset = 0
- else:
- next_offset = offset + 20 + len(data)
-
- result.append(struct.pack(b'<IIIII', len(data) - 8,
- 0xfffffffe,
- tid,
- next_offset,
- len(data) // 12))
- result.append(data)
-
- offset += 20 + len(data)
-
- # Finally, add the header (and the first TOC offset, which isn't part
- # of the header, but goes just after it)
- header = struct.pack(b'<4sIIIQQQQI', b'book',
- offset + 48,
- 0x10040000,
- 48,
- 0, 0, 0, 0, first_toc_offset)
-
- result.insert(0, header)
-
- return b''.join(result)
-
- @classmethod
- def for_file(cls, path):
- """Construct a :class:`Bookmark` for a given file."""
-
- # Find the filesystem
- st = osx.statfs(path)
- vol_path = st.f_mntonname.decode('utf-8')
-
- # Grab its attributes
- attrs = [osx.ATTR_CMN_CRTIME,
- osx.ATTR_VOL_SIZE
- | osx.ATTR_VOL_NAME
- | osx.ATTR_VOL_UUID,
- 0, 0, 0]
- volinfo = osx.getattrlist(vol_path, attrs, 0)
-
- vol_crtime = volinfo[0]
- vol_size = volinfo[1]
- vol_name = volinfo[2]
- vol_uuid = volinfo[3]
-
- # Also grab various attributes of the file
- attrs = [(osx.ATTR_CMN_OBJTYPE
- | osx.ATTR_CMN_CRTIME
- | osx.ATTR_CMN_FILEID), 0, 0, 0, 0]
- info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
-
- cnid = info[2]
- crtime = info[1]
-
- if info[0] == osx.VREG:
- flags = kCFURLResourceIsRegularFile
- elif info[0] == osx.VDIR:
- flags = kCFURLResourceIsDirectory
- elif info[0] == osx.VLNK:
- flags = kCFURLResourceIsSymbolicLink
- else:
- flags = kCFURLResourceIsRegularFile
-
- dirname, filename = os.path.split(path)
-
- relcount = 0
- if not os.path.isabs(dirname):
- curdir = os.getcwd()
- head, tail = os.path.split(curdir)
- relcount = 0
- while head and tail:
- relcount += 1
- head, tail = os.path.split(head)
- dirname = os.path.join(curdir, dirname)
-
- foldername = os.path.basename(dirname)
-
- rel_path = os.path.relpath(path, vol_path)
-
- # Build the path arrays
- name_path = []
- cnid_path = []
- head, tail = os.path.split(rel_path)
- if not tail:
- head, tail = os.path.split(head)
- while head or tail:
- if head:
- attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
- info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
- cnid_path.insert(0, info[0])
- head, tail = os.path.split(head)
- name_path.insert(0, tail)
- else:
- head, tail = os.path.split(head)
- name_path.append(filename)
- cnid_path.append(cnid)
-
- url_lengths = [relcount, len(name_path) - relcount]
-
- fileprops = Data(struct.pack(b'<QQQ', flags, 0x0f, 0))
- volprops = Data(struct.pack(b'<QQQ', 0x81 | kCFURLVolumeSupportsPersistentIDs,
- 0x13ef | kCFURLVolumeSupportsPersistentIDs, 0))
-
- toc = {
- kBookmarkPath: name_path,
- kBookmarkCNIDPath: cnid_path,
- kBookmarkFileCreationDate: crtime,
- kBookmarkFileProperties: fileprops,
- kBookmarkContainingFolder: len(name_path) - 2,
- kBookmarkVolumePath: vol_path,
- kBookmarkVolumeIsRoot: vol_path == '/',
- kBookmarkVolumeURL: URL('file://' + vol_path),
- kBookmarkVolumeName: vol_name,
- kBookmarkVolumeSize: vol_size,
- kBookmarkVolumeCreationDate: vol_crtime,
- kBookmarkVolumeUUID: str(vol_uuid).upper(),
- kBookmarkVolumeProperties: volprops,
- kBookmarkCreationOptions: 512,
- kBookmarkWasFileReference: True,
- kBookmarkUserName: 'unknown',
- kBookmarkUID: 99,
- }
-
- if relcount:
- toc[kBookmarkURLLengths] = url_lengths
-
- return Bookmark([(1, toc)])
-
- def __repr__(self):
- result = ['Bookmark([']
- for tid,toc in self.tocs:
- result.append('(0x%x, {\n' % tid)
- for k,v in iteritems(toc):
- if isinstance(k, (str, unicode)):
- kf = repr(k)
- else:
- kf = '0x%04x' % k
- result.append(' %s: %r\n' % (kf, v))
- result.append('}),\n')
- result.append('])')
-
- return ''.join(result)
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/utils.py b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/utils.py
deleted file mode 100644
index 6a7d0a121..000000000
--- a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/utils.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import datetime
-
-ZERO = datetime.timedelta(0)
-class UTC (datetime.tzinfo):
- def utcoffset(self, dt):
- return ZERO
- def dst(self, dt):
- return ZERO
- def tzname(self, dt):
- return 'UTC'
-
-utc = UTC()
-mac_epoch = datetime.datetime(1904,1,1,0,0,0,0,utc)
-unix_epoch = datetime.datetime(1970,1,1,0,0,0,0,utc)
-osx_epoch = datetime.datetime(2001,1,1,0,0,0,0,utc)
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/LICENSE b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/LICENSE
index e91f4eb38..e91f4eb38 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/LICENSE
+++ b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/LICENSE
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/__init__.py b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/__init__.py
new file mode 100644
index 000000000..5fba876a7
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/__init__.py
@@ -0,0 +1,5 @@
+from .core import build_dmg
+
+__version__ = "1.6.0"
+
+__all__ = ["__version__", "build_dmg"]
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/__main__.py b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/__main__.py
new file mode 100644
index 000000000..ca712a183
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/__main__.py
@@ -0,0 +1,58 @@
+#! /usr/bin/env python
+import argparse
+
+from .core import build_dmg
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Construct a disk image file.")
+ parser.add_argument(
+ "volume_name",
+ metavar="volume-name",
+ help="The name to give to the volume (this will appear in the title bar when the user mounts the disk image).",
+ )
+ parser.add_argument(
+ "filename",
+ metavar="output.dmg",
+ help="The filename of the disk image to create.",
+ )
+ parser.add_argument("-s", "--settings", help="The path of the settings file.")
+ parser.add_argument(
+ "-D",
+ dest="defines",
+ action="append",
+ default=[],
+ help="Define a value for the settings file (e.g. -Dfoo=bar).",
+ )
+ parser.add_argument(
+ "--no-hidpi",
+ dest="lookForHiDPI",
+ action="store_false",
+ default=True,
+ help="Do not search for HiDPI versions of the background image (if specified)",
+ )
+
+ args = parser.parse_args()
+
+ defines = {}
+ for d in args.defines:
+ k, v = d.split("=", 1)
+ k = k.strip()
+ v = v.strip()
+ if (v.startswith("'") and v.endswith("'")) or (
+ v.startswith('"') and v.endswith('"')
+ ):
+ v = v[1:-1]
+ defines[k] = v
+
+ build_dmg(
+ args.filename,
+ args.volume_name,
+ args.settings,
+ defines=defines,
+ lookForHiDPI=args.lookForHiDPI,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/badge.py b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/badge.py
new file mode 100644
index 000000000..f9764da01
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/badge.py
@@ -0,0 +1,169 @@
+import math
+
+from Quartz import (
+ CFURLCreateWithFileSystemPath,
+ CGBitmapContextCreate,
+ CGBitmapContextCreateImage,
+ CGColorSpaceCreateWithName,
+ CGImageDestinationAddImage,
+ CGImageDestinationCreateWithURL,
+ CGImageDestinationFinalize,
+ CGImageSourceCopyPropertiesAtIndex,
+ CGImageSourceCreateImageAtIndex,
+ CGImageSourceCreateWithURL,
+ CGImageSourceGetCount,
+ CIContext,
+ CIFilter,
+ CIImage,
+ CIVector,
+ NSAffineTransform,
+ kCFURLPOSIXPathStyle,
+ kCGColorSpaceGenericRGB,
+ kCGImageAlphaPremultipliedLast,
+ kCIInputAspectRatioKey,
+ kCIInputBackgroundImageKey,
+ kCIInputImageKey,
+ kCIInputScaleKey,
+ kCIOutputImageKey,
+)
+
+_REMOVABLE_DISK_PATH = (
+ "/System/Library/Extensions/IOStorageFamily.kext/Contents/Resources/Removable.icns"
+)
+
+
+def badge_disk_icon(badge_file, output_file):
+ # Load the Removable disk icon
+ url = CFURLCreateWithFileSystemPath(
+ None, _REMOVABLE_DISK_PATH, kCFURLPOSIXPathStyle, False
+ )
+ backdrop = CGImageSourceCreateWithURL(url, None)
+ backdropCount = CGImageSourceGetCount(backdrop)
+
+ # Load the badge
+ url = CFURLCreateWithFileSystemPath(None, badge_file, kCFURLPOSIXPathStyle, False)
+ badge = CGImageSourceCreateWithURL(url, None)
+ assert badge is not None, "Unable to process image file: %s" % badge_file
+ badgeCount = CGImageSourceGetCount(badge)
+
+ # Set up a destination for our target
+ url = CFURLCreateWithFileSystemPath(None, output_file, kCFURLPOSIXPathStyle, False)
+ target = CGImageDestinationCreateWithURL(url, "com.apple.icns", backdropCount, None)
+
+ # Get the RGB colorspace
+ rgbColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)
+
+ # Scale
+ scale = 1.0
+
+ # Perspective transform
+ corners = ((0.2, 0.95), (0.8, 0.95), (0.85, 0.35), (0.15, 0.35))
+
+ # Translation
+ position = (0.5, 0.5)
+
+ for n in range(backdropCount):
+ props = CGImageSourceCopyPropertiesAtIndex(backdrop, n, None)
+ width = props["PixelWidth"]
+ height = props["PixelHeight"]
+ dpi = props["DPIWidth"]
+ depth = props["Depth"]
+
+ # Choose the best sized badge image
+ bestWidth = None
+ # ?? bestHeight = None
+ bestBadge = None
+ bestDepth = None
+ # ?? bestDPI = None
+ for m in range(badgeCount):
+ badgeProps = CGImageSourceCopyPropertiesAtIndex(badge, m, None)
+ badgeWidth = badgeProps["PixelWidth"]
+ # ?? badgeHeight = badgeProps['PixelHeight']
+ badgeDPI = badgeProps["DPIWidth"]
+ badgeDepth = badgeProps["Depth"]
+
+ if bestBadge is None or (
+ badgeWidth <= width
+ and (
+ bestWidth > width
+ or badgeWidth > bestWidth
+ or (
+ badgeWidth == bestWidth
+ and badgeDPI == dpi
+ and badgeDepth <= depth
+ and (bestDepth is None or badgeDepth > bestDepth)
+ )
+ )
+ ):
+ bestBadge = m
+ bestWidth = badgeWidth
+ # ?? bestHeight = badgeHeight
+ # ?? bestDPI = badgeDPI
+ bestDepth = badgeDepth
+
+ badgeImage = CGImageSourceCreateImageAtIndex(badge, bestBadge, None)
+ badgeCI = CIImage.imageWithCGImage_(badgeImage)
+
+ backgroundImage = CGImageSourceCreateImageAtIndex(backdrop, n, None)
+ backgroundCI = CIImage.imageWithCGImage_(backgroundImage)
+
+ compositor = CIFilter.filterWithName_("CISourceOverCompositing")
+ lanczos = CIFilter.filterWithName_("CILanczosScaleTransform")
+ perspective = CIFilter.filterWithName_("CIPerspectiveTransform")
+ transform = CIFilter.filterWithName_("CIAffineTransform")
+
+ lanczos.setValue_forKey_(badgeCI, kCIInputImageKey)
+ lanczos.setValue_forKey_(scale * float(width) / bestWidth, kCIInputScaleKey)
+ lanczos.setValue_forKey_(1.0, kCIInputAspectRatioKey)
+
+ topLeft = (width * scale * corners[0][0], width * scale * corners[0][1])
+ topRight = (width * scale * corners[1][0], width * scale * corners[1][1])
+ bottomRight = (width * scale * corners[2][0], width * scale * corners[2][1])
+ bottomLeft = (width * scale * corners[3][0], width * scale * corners[3][1])
+
+ out = lanczos.valueForKey_(kCIOutputImageKey)
+ if width >= 16:
+ perspective.setValue_forKey_(out, kCIInputImageKey)
+ perspective.setValue_forKey_(
+ CIVector.vectorWithX_Y_(*topLeft), "inputTopLeft"
+ )
+ perspective.setValue_forKey_(
+ CIVector.vectorWithX_Y_(*topRight), "inputTopRight"
+ )
+ perspective.setValue_forKey_(
+ CIVector.vectorWithX_Y_(*bottomRight), "inputBottomRight"
+ )
+ perspective.setValue_forKey_(
+ CIVector.vectorWithX_Y_(*bottomLeft), "inputBottomLeft"
+ )
+ out = perspective.valueForKey_(kCIOutputImageKey)
+
+ tfm = NSAffineTransform.transform()
+ tfm.translateXBy_yBy_(
+ math.floor((position[0] - 0.5 * scale) * width),
+ math.floor((position[1] - 0.5 * scale) * height),
+ )
+
+ transform.setValue_forKey_(out, kCIInputImageKey)
+ transform.setValue_forKey_(tfm, "inputTransform")
+ out = transform.valueForKey_(kCIOutputImageKey)
+
+ compositor.setValue_forKey_(out, kCIInputImageKey)
+ compositor.setValue_forKey_(backgroundCI, kCIInputBackgroundImageKey)
+
+ result = compositor.valueForKey_(kCIOutputImageKey)
+
+ cgContext = CGBitmapContextCreate(
+ None, width, height, 8, 0, rgbColorSpace, kCGImageAlphaPremultipliedLast
+ )
+ context = CIContext.contextWithCGContext_options_(cgContext, None)
+
+ context.drawImage_inRect_fromRect_(
+ result, ((0, 0), (width, height)), ((0, 0), (width, height))
+ )
+
+ image = CGBitmapContextCreateImage(cgContext)
+
+ CGImageDestinationAddImage(target, image, props)
+
+ CGImageDestinationFinalize(target)
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/colors.py b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/colors.py
new file mode 100644
index 000000000..8cab342a7
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/colors.py
@@ -0,0 +1,504 @@
+import math
+import re
+
+
+class Color:
+ def to_rgb(self):
+ raise Exception("Must implement to_rgb() in subclasses")
+
+
+class RGB:
+ def __init__(self, r, g, b):
+ self.r = r
+ self.g = g
+ self.b = b
+
+ def to_rgb(self):
+ return self
+
+
+class HSL(Color):
+ def __init__(self, h, s, l): # noqa; E741
+ self.h = h
+ self.s = s
+ self.l = l # noqa; E741
+
+ @staticmethod
+ def _hue_to_rgb(t1, t2, hue):
+ if hue < 0:
+ hue += 6
+ elif hue >= 6:
+ hue -= 6
+
+ if hue < 1:
+ return (t2 - t1) * hue + t1
+ elif hue < 3:
+ return t2
+ elif hue < 4:
+ return (t2 - t1) * (4 - hue) + t1
+ else:
+ return t1
+
+ def to_rgb(self):
+ hue = self.h / 60.0
+ if self.l <= 0.5: # noqa; E741
+ t2 = self.l * (self.s + 1)
+ else:
+ t2 = self.l + self.s - (self.l * self.s)
+ t1 = self.l * 2 - t2
+ r = self._hue_to_rgb(t1, t2, hue + 2)
+ g = self._hue_to_rgb(t1, t2, hue)
+ b = self._hue_to_rgb(t1, t2, hue - 2)
+ return RGB(r, g, b)
+
+
+class HWB(Color):
+ def __init__(self, h, w, b):
+ self.h = h
+ self.w = w
+ self.b = b
+
+ @staticmethod
+ def _hue_to_rgb(hue):
+ if hue < 0:
+ hue += 6
+ elif hue >= 6:
+ hue -= 6
+
+ if hue < 1:
+ return hue
+ elif hue < 3:
+ return 1
+ elif hue < 4:
+ return 4 - hue
+ else:
+ return 0
+
+ def to_rgb(self):
+ hue = self.h / 60.0
+ t1 = 1 - self.w - self.b
+ r = self._hue_to_rgb(hue + 2) * t1 + self.w
+ g = self._hue_to_rgb(hue) * t1 + self.w
+ b = self._hue_to_rgb(hue - 2) * t1 + self.w
+ return RGB(r, g, b)
+
+
+class CMYK(Color):
+ def __init__(self, c, m, y, k):
+ self.c = c
+ self.m = m
+ self.y = y
+ self.k = k
+
+ def to_rgb(self):
+ r = 1.0 - min(1.0, self.c + self.k)
+ g = 1.0 - min(1.0, self.m + self.k)
+ b = 1.0 - min(1.0, self.y + self.k)
+ return RGB(r, g, b)
+
+
+class Gray(Color):
+ def __init__(self, g):
+ self.g = g
+
+ def to_rgb(self):
+ return RGB(self.g, self.g, self.g)
+
+
+_x11_colors = {
+ "aliceblue": (240, 248, 255),
+ "antiquewhite": (250, 235, 215),
+ "aqua": (0, 255, 255),
+ "aquamarine": (127, 255, 212),
+ "azure": (240, 255, 255),
+ "beige": (245, 245, 220),
+ "bisque": (255, 228, 196),
+ "black": (0, 0, 0),
+ "blanchedalmond": (255, 235, 205),
+ "blue": (0, 0, 255),
+ "blueviolet": (138, 43, 226),
+ "brown": (165, 42, 42),
+ "burlywood": (222, 184, 135),
+ "cadetblue": (95, 158, 160),
+ "chartreuse": (127, 255, 0),
+ "chocolate": (210, 105, 30),
+ "coral": (255, 127, 80),
+ "cornflowerblue": (100, 149, 237),
+ "cornsilk": (255, 248, 220),
+ "crimson": (220, 20, 60),
+ "cyan": (0, 255, 255),
+ "darkblue": (0, 0, 139),
+ "darkcyan": (0, 139, 139),
+ "darkgoldenrod": (184, 134, 11),
+ "darkgray": (169, 169, 169),
+ "darkgreen": (0, 100, 0),
+ "darkgrey": (169, 169, 169),
+ "darkkhaki": (189, 183, 107),
+ "darkmagenta": (139, 0, 139),
+ "darkolivegreen": (85, 107, 47),
+ "darkorange": (255, 140, 0),
+ "darkorchid": (153, 50, 204),
+ "darkred": (139, 0, 0),
+ "darksalmon": (233, 150, 122),
+ "darkseagreen": (143, 188, 143),
+ "darkslateblue": (72, 61, 139),
+ "darkslategray": (47, 79, 79),
+ "darkslategrey": (47, 79, 79),
+ "darkturquoise": (0, 206, 209),
+ "darkviolet": (148, 0, 211),
+ "deeppink": (255, 20, 147),
+ "deepskyblue": (0, 191, 255),
+ "dimgray": (105, 105, 105),
+ "dimgrey": (105, 105, 105),
+ "dodgerblue": (30, 144, 255),
+ "firebrick": (178, 34, 34),
+ "floralwhite": (255, 250, 240),
+ "forestgreen": (34, 139, 34),
+ "fuchsia": (255, 0, 255),
+ "gainsboro": (220, 220, 220),
+ "ghostwhite": (248, 248, 255),
+ "gold": (255, 215, 0),
+ "goldenrod": (218, 165, 32),
+ "gray": (128, 128, 128),
+ "grey": (128, 128, 128),
+ "green": (0, 128, 0),
+ "greenyellow": (173, 255, 47),
+ "honeydew": (240, 255, 240),
+ "hotpink": (255, 105, 180),
+ "indianred": (205, 92, 92),
+ "indigo": (75, 0, 130),
+ "ivory": (255, 255, 240),
+ "khaki": (240, 230, 140),
+ "lavender": (230, 230, 250),
+ "lavenderblush": (255, 240, 245),
+ "lawngreen": (124, 252, 0),
+ "lemonchiffon": (255, 250, 205),
+ "lightblue": (173, 216, 230),
+ "lightcoral": (240, 128, 128),
+ "lightcyan": (224, 255, 255),
+ "lightgoldenrodyellow": (250, 250, 210),
+ "lightgray": (211, 211, 211),
+ "lightgreen": (144, 238, 144),
+ "lightgrey": (211, 211, 211),
+ "lightpink": (255, 182, 193),
+ "lightsalmon": (255, 160, 122),
+ "lightseagreen": (32, 178, 170),
+ "lightskyblue": (135, 206, 250),
+ "lightslategray": (119, 136, 153),
+ "lightslategrey": (119, 136, 153),
+ "lightsteelblue": (176, 196, 222),
+ "lightyellow": (255, 255, 224),
+ "lime": (0, 255, 0),
+ "limegreen": (50, 205, 50),
+ "linen": (250, 240, 230),
+ "magenta": (255, 0, 255),
+ "maroon": (128, 0, 0),
+ "mediumaquamarine": (102, 205, 170),
+ "mediumblue": (0, 0, 205),
+ "mediumorchid": (186, 85, 211),
+ "mediumpurple": (147, 112, 219),
+ "mediumseagreen": (60, 179, 113),
+ "mediumslateblue": (123, 104, 238),
+ "mediumspringgreen": (0, 250, 154),
+ "mediumturquoise": (72, 209, 204),
+ "mediumvioletred": (199, 21, 133),
+ "midnightblue": (25, 25, 112),
+ "mintcream": (245, 255, 250),
+ "mistyrose": (255, 228, 225),
+ "moccasin": (255, 228, 181),
+ "navajowhite": (255, 222, 173),
+ "navy": (0, 0, 128),
+ "oldlace": (253, 245, 230),
+ "olive": (128, 128, 0),
+ "olivedrab": (107, 142, 35),
+ "orange": (255, 165, 0),
+ "orangered": (255, 69, 0),
+ "orchid": (218, 112, 214),
+ "palegoldenrod": (238, 232, 170),
+ "palegreen": (152, 251, 152),
+ "paleturquoise": (175, 238, 238),
+ "palevioletred": (219, 112, 147),
+ "papayawhip": (255, 239, 213),
+ "peachpuff": (255, 218, 185),
+ "peru": (205, 133, 63),
+ "pink": (255, 192, 203),
+ "plum": (221, 160, 221),
+ "powderblue": (176, 224, 230),
+ "purple": (128, 0, 128),
+ "red": (255, 0, 0),
+ "rosybrown": (188, 143, 143),
+ "royalblue": (65, 105, 225),
+ "saddlebrown": (139, 69, 19),
+ "salmon": (250, 128, 114),
+ "sandybrown": (244, 164, 96),
+ "seagreen": (46, 139, 87),
+ "seashell": (255, 245, 238),
+ "sienna": (160, 82, 45),
+ "silver": (192, 192, 192),
+ "skyblue": (135, 206, 235),
+ "slateblue": (106, 90, 205),
+ "slategray": (112, 128, 144),
+ "slategrey": (112, 128, 144),
+ "snow": (255, 250, 250),
+ "springgreen": (0, 255, 127),
+ "steelblue": (70, 130, 180),
+ "tan": (210, 180, 140),
+ "teal": (0, 128, 128),
+ "thistle": (216, 191, 216),
+ "tomato": (255, 99, 71),
+ "turquoise": (64, 224, 208),
+ "violet": (238, 130, 238),
+ "wheat": (245, 222, 179),
+ "white": (255, 255, 255),
+ "whitesmoke": (245, 245, 245),
+ "yellow": (255, 255, 0),
+ "yellowgreen": (154, 205, 50),
+}
+
+_ws_re = re.compile(r"\s+")
+_token_re = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
+_hex_re = re.compile(r"#([0-9a-f]{3}(?:[0-9a-f]{3})?)$")
+_number_re = re.compile(r"[0-9]*(\.[0-9]*)")
+
+
+class ColorParser:
+ def __init__(self, s):
+ self._string = s
+ self._pos = 0
+
+ def skipws(self):
+ m = _ws_re.match(self._string, self._pos)
+ if m:
+ self._pos = m.end(0)
+
+ def expect(self, s, context=""):
+ if (
+ len(self._string) - self._pos < len(s)
+ or self._string[self._pos : self._pos + len(s)] != s
+ ):
+ raise ValueError(f'bad color "{self._string}" - expected "{s}"{context}')
+ self._pos += len(s)
+
+ def expectEnd(self):
+ if self._pos != len(self._string):
+ raise ValueError('junk at end of color "%s"' % self._string)
+
+ def getToken(self):
+ m = _token_re.match(self._string, self._pos)
+ if m:
+ token = m.group(0)
+
+ self._pos = m.end(0)
+ return token
+ return None
+
+ def parseNumber(self, context=""):
+ m = _number_re.match(self._string, self._pos)
+ if m:
+ self._pos = m.end(0)
+ return float(m.group(0))
+ raise ValueError(f'bad color "{self._string}" - expected a number{context}')
+
+ def parseColor(self):
+ self.skipws()
+
+ token = self.getToken()
+ if token:
+ if token == "rgb":
+ return self.parseRGB()
+ elif token == "hsl":
+ return self.parseHSL()
+ elif token == "hwb":
+ return self.parseHWB()
+ elif token == "cmyk":
+ return self.parseCMYK()
+ elif token == "gray" or token == "grey":
+ return self.parseGray()
+
+ try:
+ r, g, b = _x11_colors[token]
+ except KeyError:
+ raise ValueError('unknown color name "%s"' % token)
+
+ self.expectEnd()
+
+ return RGB(r / 255.0, g / 255.0, b / 255.0)
+
+ m = _hex_re.match(self._string, self._pos)
+ if m:
+ hrgb = m.group(1)
+
+ if len(hrgb) == 3:
+ r = int("0x" + 2 * hrgb[0], 16)
+ g = int("0x" + 2 * hrgb[1], 16)
+ b = int("0x" + 2 * hrgb[2], 16)
+ else:
+ r = int("0x" + hrgb[0:2], 16)
+ g = int("0x" + hrgb[2:4], 16)
+ b = int("0x" + hrgb[4:6], 16)
+
+ self._pos = m.end(0)
+ self.skipws()
+
+ self.expectEnd()
+
+ return RGB(r / 255.0, g / 255.0, b / 255.0)
+
+ raise ValueError('bad color syntax "%s"' % self._string)
+
+ def parseRGB(self):
+ self.expect("(", 'after "rgb"')
+ self.skipws()
+
+ r = self.parseValue()
+
+ self.skipws()
+ self.expect(",", 'in "rgb"')
+ self.skipws()
+
+ g = self.parseValue()
+
+ self.skipws()
+ self.expect(",", 'in "rgb"')
+ self.skipws()
+
+ b = self.parseValue()
+
+ self.skipws()
+ self.expect(")", 'at end of "rgb"')
+
+ self.skipws()
+ self.expectEnd()
+
+ return RGB(r, g, b)
+
+ def parseHSL(self):
+ self.expect("(", 'after "hsl"')
+ self.skipws()
+
+ h = self.parseAngle()
+
+ self.skipws()
+ self.expect(",", 'in "hsl"')
+ self.skipws()
+
+ s = self.parseValue()
+
+ self.skipws()
+ self.expect(",", 'in "hsl"')
+ self.skipws()
+
+ l = self.parseValue() # noqa; E741
+
+ self.skipws()
+ self.expect(")", 'at end of "hsl"')
+
+ self.skipws()
+ self.expectEnd()
+
+ return HSL(h, s, l)
+
+ def parseHWB(self):
+ self.expect("(", 'after "hwb"')
+ self.skipws()
+
+ h = self.parseAngle()
+
+ self.skipws()
+ self.expect(",", 'in "hwb"')
+ self.skipws()
+
+ w = self.parseValue()
+
+ self.skipws()
+ self.expect(",", 'in "hwb"')
+ self.skipws()
+
+ b = self.parseValue()
+
+ self.skipws()
+ self.expect(")", 'at end of "hwb"')
+
+ self.skipws()
+ self.expectEnd()
+
+ return HWB(h, w, b)
+
+ def parseCMYK(self):
+ self.expect("(", 'after "cmyk"')
+ self.skipws()
+
+ c = self.parseValue()
+
+ self.skipws()
+ self.expect(",", 'in "cmyk"')
+ self.skipws()
+
+ m = self.parseValue()
+
+ self.skipws()
+ self.expect(",", 'in "cmyk"')
+ self.skipws()
+
+ y = self.parseValue()
+
+ self.skipws()
+ self.expect(",", 'in "cmyk"')
+ self.skipws()
+
+ k = self.parseValue()
+
+ self.skipws()
+ self.expect(")", 'at end of "cmyk"')
+
+ self.skipws()
+ self.expectEnd()
+
+ return CMYK(c, m, y, k)
+
+ def parseGray(self):
+ self.expect("(", 'after "gray"')
+ self.skipws()
+
+ g = self.parseValue()
+
+ self.skipws()
+ self.expect(")", 'at end of "gray')
+
+ self.skipws()
+ self.expectEnd()
+
+ return Gray(g)
+
+ def parseValue(self):
+ n = self.parseNumber()
+ self.skipws()
+ if self._string[self._pos] == "%":
+ n = n / 100.0
+ self.pos += 1
+ return n
+
+ def parseAngle(self):
+ n = self.parseNumber()
+ self.skipws()
+ tok = self.getToken()
+ if tok == "rad":
+ n = n * 180.0 / math.pi
+ elif tok == "grad" or tok == "gon":
+ n = n * 0.9
+ elif tok != "deg":
+ raise ValueError('bad angle unit "%s"' % tok)
+ return n
+
+
+_color_re = re.compile(
+ r"\s*(#|rgb|hsl|hwb|cmyk|gray|grey|%s)" % "|".join(_x11_colors.keys())
+)
+
+
+def isAColor(s):
+ return _color_re.match(s)
+
+
+def parseColor(s):
+ return ColorParser(s).parseColor()
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/core.py b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/core.py
new file mode 100644
index 000000000..b514f77e9
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/core.py
@@ -0,0 +1,943 @@
+import json
+import os
+import platform
+import plistlib
+import re
+import shutil
+import subprocess
+import tempfile
+import time
+import tokenize
+
+try:
+ import importlib_resources as resources
+except ImportError:
+ from importlib import resources
+
+from ds_store import DSStore
+from mac_alias import Alias, Bookmark
+
+from . import colors, licensing
+
+try:
+ from . import badge
+except ImportError:
+ badge = None
+
+
+_hexcolor_re = re.compile(r"#[0-9a-f]{3}(?:[0-9a-f]{3})?")
+
+# The first element in the platform.mac_ver() tuple is a string containing the
+# macOS version (e.g., '10.15.6'). Parse into an integer tuple.
+MACOS_VERSION = tuple(int(v) for v in platform.mac_ver()[0].split("."))
+
+
+class DMGError(Exception):
+ def __init__(self, callback, message):
+ self.message = message
+ callback({"type": "error::fatal", "message": message})
+
+ def __str__(self):
+ return str(self.message)
+
+
+def quiet_callback(info):
+ pass
+
+
+def hdiutil(cmd, *args, **kwargs):
+ plist = kwargs.get("plist", True)
+ all_args = ["/usr/bin/hdiutil", cmd]
+ all_args.extend(args)
+ if plist:
+ all_args.append("-plist")
+ p = subprocess.Popen(all_args, stdout=subprocess.PIPE, close_fds=True)
+ output, errors = p.communicate()
+ if plist:
+ results = plistlib.loads(output)
+ else:
+ results = output
+ retcode = p.wait()
+ return retcode, results
+
+
+def load_settings(filename, settings):
+ encoding = "utf-8"
+ with open(filename, "rb") as fp:
+ try:
+ encoding = tokenize.detect_encoding(fp.readline)[0]
+ except SyntaxError:
+ pass
+
+ with open(filename, encoding=encoding) as fp:
+ exec(compile(fp.read(), filename, "exec"), settings, settings)
+
+
+def load_json(filename, settings):
+ """Read an appdmg .json spec.
+
+ Uses the defaults for appdmg, rather than the usual defaults for
+ dmgbuild.
+ """
+
+ with open(filename) as fp:
+ json_data = json.load(fp)
+
+ if "title" not in json_data:
+ raise ValueError("missing 'title' in JSON settings file")
+ if "contents" not in json_data:
+ raise ValueError("missing 'contents' in JSON settings file")
+
+ settings["volume_name"] = json_data["title"]
+ settings["icon"] = json_data.get("icon", None)
+ settings["badge_icon"] = json_data.get("badge-icon", None)
+ bk = json_data.get("background", None)
+ if bk is None:
+ bk = json_data.get("background-color", None)
+ if bk is not None:
+ settings["background"] = bk
+ settings["icon_size"] = json_data.get("icon-size", 80)
+ wnd = json_data.get("window", {"position": (100, 100), "size": (640, 480)})
+ pos = wnd.get("position", {"x": 100, "y": 100})
+ siz = wnd.get("size", {"width": 640, "height": 480})
+ settings["window_rect"] = (
+ (pos.get("x", 100), pos.get("y", 100)),
+ (siz.get("width", 640), siz.get("height", 480)),
+ )
+ settings["format"] = json_data.get("format", "UDZO")
+ settings["filesystem"] = json_data.get("filesystem", "HFS+")
+ settings["compression_level"] = json_data.get("compression-level", None)
+ settings["license"] = json_data.get("license", None)
+ files = []
+ hide = []
+ hide_extensions = []
+ symlinks = {}
+ icon_locations = {}
+ for fileinfo in json_data.get("contents", []):
+ if "path" not in fileinfo:
+ raise ValueError("missing 'path' in contents in JSON settings file")
+ if "x" not in fileinfo:
+ raise ValueError("missing 'x' in contents in JSON settings file")
+ if "y" not in fileinfo:
+ raise ValueError("missing 'y' in contents in JSON settings file")
+
+ kind = fileinfo.get("type", "file")
+ path = fileinfo["path"]
+ name = fileinfo.get("name", os.path.basename(path.rstrip("/")))
+ if kind == "file":
+ files.append((path, name))
+ elif kind == "link":
+ symlinks[name] = path
+ elif kind == "position":
+ pass
+ icon_locations[name] = (fileinfo["x"], fileinfo["y"])
+ hide_ext = fileinfo.get("hide_extension", False)
+ if hide_ext:
+ hide_extensions.append(name)
+ hidden = fileinfo.get("hidden", False)
+ if hidden:
+ hide.append(name)
+
+ settings["files"] = files
+ settings["hide_extensions"] = hide_extensions
+ settings["hide"] = hide
+ settings["symlinks"] = symlinks
+ settings["icon_locations"] = icon_locations
+
+
+def build_dmg( # noqa; C901
+ filename,
+ volume_name,
+ settings_file=None,
+ settings={},
+ defines={},
+ lookForHiDPI=True,
+ detach_retries=5,
+ callback=quiet_callback,
+):
+ options = {
+ # Default settings
+ "filename": filename,
+ "volume_name": volume_name,
+ "format": "UDBZ",
+ "filesystem": "HFS+",
+ "compression_level": None,
+ "size": None,
+ "files": [],
+ "symlinks": {},
+ "hide": [],
+ "hide_extensions": [],
+ "icon": None,
+ "badge_icon": None,
+ "background": None,
+ "show_status_bar": False,
+ "show_tab_view": False,
+ "show_toolbar": False,
+ "show_pathbar": False,
+ "show_sidebar": False,
+ "sidebar_width": 180,
+ "arrange_by": None,
+ "grid_offset": (0, 0),
+ "grid_spacing": 100.0,
+ "scroll_position": (0.0, 0.0),
+ "show_icon_preview": False,
+ "show_item_info": False,
+ "label_pos": "bottom",
+ "text_size": 16.0,
+ "icon_size": 128.0,
+ "include_icon_view_settings": "auto",
+ "include_list_view_settings": "auto",
+ "list_icon_size": 16.0,
+ "list_text_size": 12.0,
+ "list_scroll_position": (0, 0),
+ "list_sort_by": "name",
+ "list_use_relative_dates": True,
+ "list_calculate_all_sizes": False,
+ "list_columns": ("name", "date-modified", "size", "kind", "date-added"),
+ "list_column_widths": {
+ "name": 300,
+ "date-modified": 181,
+ "date-created": 181,
+ "date-added": 181,
+ "date-last-opened": 181,
+ "size": 97,
+ "kind": 115,
+ "label": 100,
+ "version": 75,
+ "comments": 300,
+ },
+ "list_column_sort_directions": {
+ "name": "ascending",
+ "date-modified": "descending",
+ "date-created": "descending",
+ "date-added": "descending",
+ "date-last-opened": "descending",
+ "size": "descending",
+ "kind": "ascending",
+ "label": "ascending",
+ "version": "ascending",
+ "comments": "ascending",
+ },
+ "window_rect": ((100, 100), (640, 280)),
+ "default_view": "icon-view",
+ "icon_locations": {},
+ "license": None,
+ "defines": defines,
+ }
+
+ callback({"type": "build::started"})
+
+ # Execute the settings file
+ if settings_file:
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "settings::load",
+ }
+ )
+
+ # We now support JSON settings files using appdmg's format
+ if settings_file.endswith(".json"):
+ load_json(settings_file, options)
+ else:
+ load_settings(settings_file, options)
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "settings::load",
+ }
+ )
+
+ # Add any overrides
+ options.update(settings)
+
+ # Set up the finder data
+ bounds = options["window_rect"]
+
+ bounds_string = "{{{{{}, {}}}, {{{}, {}}}}}".format(
+ bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]
+ )
+ bwsp = {
+ "ShowStatusBar": options["show_status_bar"],
+ "WindowBounds": bounds_string,
+ "ContainerShowSidebar": False,
+ "PreviewPaneVisibility": False,
+ "SidebarWidth": options["sidebar_width"],
+ "ShowTabView": options["show_tab_view"],
+ "ShowToolbar": options["show_toolbar"],
+ "ShowPathbar": options["show_pathbar"],
+ "ShowSidebar": options["show_sidebar"],
+ }
+
+ arrange_options = {
+ "name": "name",
+ "date-modified": "dateModified",
+ "date-created": "dateCreated",
+ "date-added": "dateAdded",
+ "date-last-opened": "dateLastOpened",
+ "size": "size",
+ "kind": "kind",
+ "label": "label",
+ }
+
+ icvp = {
+ "viewOptionsVersion": 1,
+ "backgroundType": 0,
+ "backgroundColorRed": 1.0,
+ "backgroundColorGreen": 1.0,
+ "backgroundColorBlue": 1.0,
+ "gridOffsetX": float(options["grid_offset"][0]),
+ "gridOffsetY": float(options["grid_offset"][1]),
+ "gridSpacing": float(options["grid_spacing"]),
+ "arrangeBy": str(arrange_options.get(options["arrange_by"], "none")),
+ "showIconPreview": bool(options["show_icon_preview"]),
+ "showItemInfo": bool(options["show_item_info"]),
+ "labelOnBottom": options["label_pos"] == "bottom",
+ "textSize": float(options["text_size"]),
+ "iconSize": float(options["icon_size"]),
+ "scrollPositionX": float(options["scroll_position"][0]),
+ "scrollPositionY": float(options["scroll_position"][1]),
+ }
+
+ background = options["background"]
+
+ columns = {
+ "name": "name",
+ "date-modified": "dateModified",
+ "date-created": "dateCreated",
+ "date-added": "dateAdded",
+ "date-last-opened": "dateLastOpened",
+ "size": "size",
+ "kind": "kind",
+ "label": "label",
+ "version": "version",
+ "comments": "comments",
+ }
+
+ default_widths = {
+ "name": 300,
+ "date-modified": 181,
+ "date-created": 181,
+ "date-added": 181,
+ "date-last-opened": 181,
+ "size": 97,
+ "kind": 115,
+ "label": 100,
+ "version": 75,
+ "comments": 300,
+ }
+
+ default_sort_directions = {
+ "name": "ascending",
+ "date-modified": "descending",
+ "date-created": "descending",
+ "date-added": "descending",
+ "date-last-opened": "descending",
+ "size": "descending",
+ "kind": "ascending",
+ "label": "ascending",
+ "version": "ascending",
+ "comments": "ascending",
+ }
+
+ lsvp = {
+ "viewOptionsVersion": 1,
+ "sortColumn": columns.get(options["list_sort_by"], "name"),
+ "textSize": float(options["list_text_size"]),
+ "iconSize": float(options["list_icon_size"]),
+ "showIconPreview": options["show_icon_preview"],
+ "scrollPositionX": options["list_scroll_position"][0],
+ "scrollPositionY": options["list_scroll_position"][1],
+ "useRelativeDates": options["list_use_relative_dates"],
+ "calculateAllSizes": options["list_calculate_all_sizes"],
+ }
+
+ lsvp["columns"] = {}
+ cndx = {}
+
+ for n, column in enumerate(options["list_columns"]):
+ cndx[column] = n
+ width = options["list_column_widths"].get(column, default_widths[column])
+ asc = "ascending" == options["list_column_sort_directions"].get(
+ column, default_sort_directions[column]
+ )
+
+ lsvp["columns"][columns[column]] = {
+ "index": n,
+ "width": width,
+ "identifier": columns[column],
+ "visible": True,
+ "ascending": asc,
+ }
+
+ n = len(options["list_columns"])
+ for k in columns:
+ if cndx.get(k, None) is None:
+ cndx[k] = n
+ width = default_widths[k]
+ asc = "ascending" == default_sort_directions[k]
+
+ lsvp["columns"][columns[column]] = {
+ "index": n,
+ "width": width,
+ "identifier": columns[column],
+ "visible": False,
+ "ascending": asc,
+ }
+
+ n += 1
+
+ default_view = options["default_view"]
+ views = {
+ "icon-view": b"icnv",
+ "column-view": b"clmv",
+ "list-view": b"Nlsv",
+ "coverflow": b"Flwv",
+ }
+
+ icvl = (b"type", views.get(default_view, "icnv"))
+
+ include_icon_view_settings = default_view == "icon-view" or options[
+ "include_icon_view_settings"
+ ] not in ("auto", "no", 0, False, None)
+ include_list_view_settings = default_view in ("list-view", "coverflow") or options[
+ "include_list_view_settings"
+ ] not in ("auto", "no", 0, False, None)
+
+ filename = options["filename"]
+ volume_name = options["volume_name"]
+
+ # Construct a writeable image to start with
+ dirname, basename = os.path.split(os.path.realpath(filename))
+ if not basename.endswith(".dmg"):
+ basename += ".dmg"
+ writableFile = tempfile.NamedTemporaryFile(
+ dir=dirname, prefix=".temp", suffix=basename
+ )
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "size::calculate",
+ }
+ )
+
+ total_size = options["size"]
+ if total_size is None:
+ # Start with a size of 128MB - this way we don't need to calculate the
+ # size of the background image, volume icon, and .DS_Store file (and
+ # 128 MB should be well sufficient for even the most outlandish image
+ # sizes, like an uncompressed 5K multi-resolution TIFF)
+ total_size = 128 * 1024 * 1024
+
+ def roundup(x, n):
+ return x if x % n == 0 else x + n - x % n
+
+ for path in options["files"]:
+ if isinstance(path, tuple):
+ path = path[0]
+
+ if not os.path.islink(path) and os.path.isdir(path):
+ for dirpath, dirnames, filenames in os.walk(path):
+ for f in filenames:
+ fp = os.path.join(dirpath, f)
+ total_size += roundup(os.lstat(fp).st_size, 4096)
+ else:
+ total_size += roundup(os.lstat(path).st_size, 4096)
+
+ for name, target in options["symlinks"].items():
+ total_size += 4096
+
+ total_size = str(max(total_size / 1000, 1024)) + "K"
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "size::calculate",
+ "size": total_size,
+ }
+ )
+
+ callback(
+ {
+ "type": "command::start",
+ "command": "hdiutil::create",
+ }
+ )
+
+ filesystem = options["filesystem"].upper()
+ fs_args = "-c c=64,a=16,e=16" if filesystem != "APFS" else ""
+
+ ret, output = hdiutil(
+ "create",
+ "-ov",
+ "-volname",
+ volume_name,
+ "-fs",
+ filesystem,
+ "-fsargs",
+ fs_args,
+ "-size",
+ total_size,
+ writableFile.name,
+ )
+
+ callback(
+ {
+ "type": "command::finished",
+ "command": "hdiutil::create",
+ "ret": ret,
+ "output": output,
+ }
+ )
+
+ if ret:
+ raise DMGError(callback, "Unable to create disk image")
+
+ callback(
+ {
+ "type": "command::start",
+ "command": "hdiutil::attach",
+ }
+ )
+
+ # IDME was deprecated in macOS 10.15/Catalina; as a result, use of -noidme
+ # started raising a warning.
+ if MACOS_VERSION >= (10, 15):
+ ret, output = hdiutil(
+ "attach", "-nobrowse", "-owners", "off", writableFile.name
+ )
+ else:
+ ret, output = hdiutil(
+ "attach", "-nobrowse", "-owners", "off", "-noidme", writableFile.name
+ )
+
+ callback(
+ {
+ "type": "command::finished",
+ "command": "hdiutil::attach",
+ "ret": ret,
+ "output": output,
+ }
+ )
+
+ if ret:
+ raise DMGError(callback, "Unable to attach disk image")
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "dmg::create",
+ }
+ )
+
+ try:
+ for info in output["system-entities"]:
+ if info.get("mount-point", None):
+ device = info["dev-entry"]
+ mount_point = info["mount-point"]
+
+ icon = options["icon"]
+ if badge:
+ badge_icon = options["badge_icon"]
+ else:
+ badge_icon = None
+ icon_target_path = os.path.join(mount_point, ".VolumeIcon.icns")
+ if icon:
+ shutil.copyfile(icon, icon_target_path)
+ elif badge_icon:
+ badge.badge_disk_icon(badge_icon, icon_target_path)
+
+ if icon or badge_icon:
+ subprocess.call(["/usr/bin/SetFile", "-a", "C", mount_point])
+
+ background_bmk = None
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "background::create",
+ }
+ )
+
+ if not isinstance(background, str):
+ pass
+ elif colors.isAColor(background):
+ c = colors.parseColor(background).to_rgb()
+
+ icvp["backgroundType"] = 1
+ icvp["backgroundColorRed"] = float(c.r)
+ icvp["backgroundColorGreen"] = float(c.g)
+ icvp["backgroundColorBlue"] = float(c.b)
+ else:
+ if os.path.isfile(background):
+ # look to see if there are HiDPI resources available
+
+ if lookForHiDPI is True:
+ name, extension = os.path.splitext(os.path.basename(background))
+ orderedImages = [background]
+ imageDirectory = os.path.dirname(background)
+ if imageDirectory == "":
+ imageDirectory = "."
+ for candidateName in os.listdir(imageDirectory):
+ hasScale = re.match(
+ r"^(?P<name>.+)@(?P<scale>\d+)x(?P<extension>\.\w+)$",
+ candidateName,
+ )
+ if (
+ hasScale
+ and name == hasScale.group("name")
+ and extension == hasScale.group("extension")
+ ):
+ scale = int(hasScale.group("scale"))
+ if len(orderedImages) < scale:
+ orderedImages += [None] * (scale - len(orderedImages))
+ orderedImages[scale - 1] = os.path.join(
+ imageDirectory, candidateName
+ )
+
+ if len(orderedImages) > 1:
+ # compile the grouped tiff
+ backgroundFile = tempfile.NamedTemporaryFile(suffix=".tiff")
+ background = backgroundFile.name
+ output = tempfile.TemporaryFile(mode="w+")
+ try:
+ subprocess.check_call(
+ ["/usr/bin/tiffutil", "-cathidpicheck"]
+ + list(filter(None, orderedImages))
+ + ["-out", background],
+ stdout=output,
+ stderr=output,
+ )
+ except Exception as e:
+ output.seek(0)
+ raise ValueError(
+ 'unable to compile combined HiDPI file "%s" got error: %s\noutput: %s'
+ % (background, str(e), output.read())
+ )
+
+ _, kind = os.path.splitext(background)
+ path_in_image = os.path.join(mount_point, ".background" + kind)
+ shutil.copyfile(background, path_in_image)
+ else:
+ dmg_resources = resources.files("dmgbuild")
+ bg_resource = dmg_resources.joinpath(
+ "resources/" + background + ".tiff"
+ )
+ if bg_resource.is_file():
+ path_in_image = os.path.join(mount_point, ".background.tiff")
+ with bg_resource.open("rb") as in_file:
+ with open(path_in_image, "wb") as out_file:
+ out_file.write(in_file.read())
+ else:
+ raise ValueError('background file "%s" not found' % background)
+
+ alias = Alias.for_file(path_in_image)
+ background_bmk = Bookmark.for_file(path_in_image)
+
+ icvp["backgroundType"] = 2
+ icvp["backgroundImageAlias"] = alias.to_bytes()
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "background::create",
+ }
+ )
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "files::add",
+ "total": len(options["files"]),
+ }
+ )
+
+ for f in options["files"]:
+ if isinstance(f, tuple):
+ f_in_image = os.path.join(mount_point, f[1])
+ f = f[0]
+ else:
+ basename = os.path.basename(f.rstrip("/"))
+ f_in_image = os.path.join(mount_point, basename)
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "file::add",
+ "file": f_in_image,
+ }
+ )
+
+ # use system ditto command to preserve code signing, etc.
+ subprocess.call(["/usr/bin/ditto", f, f_in_image])
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "file::add",
+ "file": f_in_image,
+ }
+ )
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "files::add",
+ }
+ )
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "symlinks::add",
+ "total": len(options["symlinks"]),
+ }
+ )
+
+ for name, target in options["symlinks"].items():
+ name_in_image = os.path.join(mount_point, name)
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "symlink::add",
+ "file": name_in_image,
+ "target": target,
+ }
+ )
+ os.symlink(target, name_in_image)
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "symlink::add",
+ "file": name_in_image,
+ "target": target,
+ }
+ )
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "symlinks::add",
+ }
+ )
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "extensions::hide",
+ }
+ )
+
+ to_hide = []
+ for name in options["hide_extensions"]:
+ name_in_image = os.path.join(mount_point, name)
+ to_hide.append(name_in_image)
+
+ if to_hide:
+ subprocess.call(["/usr/bin/SetFile", "-a", "E"] + to_hide)
+
+ to_hide = []
+ for name in options["hide"]:
+ name_in_image = os.path.join(mount_point, name)
+ to_hide.append(name_in_image)
+
+ if to_hide:
+ subprocess.call(["/usr/bin/SetFile", "-a", "V"] + to_hide)
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "extensions::hide",
+ }
+ )
+
+ userfn = options.get("create_hook", None)
+ if callable(userfn):
+ userfn(mount_point, options)
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "dsstore::create",
+ }
+ )
+
+ image_dsstore = os.path.join(mount_point, ".DS_Store")
+
+ with DSStore.open(image_dsstore, "w+") as d:
+ d["."]["vSrn"] = ("long", 1)
+ d["."]["bwsp"] = bwsp
+ if include_icon_view_settings:
+ d["."]["icvp"] = icvp
+ if background_bmk:
+ d["."]["pBBk"] = background_bmk
+ if include_list_view_settings:
+ d["."]["lsvp"] = lsvp
+ d["."]["icvl"] = icvl
+
+ for k, v in options["icon_locations"].items():
+ d[k]["Iloc"] = v
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "dsstore::create",
+ }
+ )
+
+ # Delete .Trashes, if it gets created
+ shutil.rmtree(os.path.join(mount_point, ".Trashes"), True)
+ except Exception:
+ # Always try to detach
+ hdiutil("detach", "-force", device, plist=False)
+ raise
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "dmg::create",
+ }
+ )
+
+ # Flush writes before attempting to detach.
+ subprocess.check_call(("sync", "--file-system", mount_point))
+
+ for tries in range(detach_retries):
+ callback(
+ {
+ "type": "command::start",
+ "command": "hdiutil::detach",
+ }
+ )
+
+ ret, output = hdiutil("detach", device, plist=False)
+
+ callback(
+ {
+ "type": "command::finished",
+ "command": "hdiutil::detach",
+ "ret:": ret,
+ "output:": output,
+ }
+ )
+
+ if not ret:
+ break
+ time.sleep(1)
+
+ if ret:
+ hdiutil("detach", "-force", device, plist=False)
+ raise DMGError(callback, "Unable to detach device cleanly")
+
+ callback(
+ {
+ "type": "command::start",
+ "command": "hdiutil::resize",
+ }
+ )
+
+ # Shrink the output to the minimum possible size
+ ret, output = hdiutil(
+ "resize", "-quiet", "-sectors", "min", writableFile.name, plist=False
+ )
+
+ callback(
+ {
+ "type": "command::finished",
+ "command": "hdiutil::resize",
+ "ret": ret,
+ "output": output,
+ }
+ )
+
+ if ret:
+ raise DMGError(callback, "Unable to shrink")
+
+ callback(
+ {
+ "type": "operation::start",
+ "operation": "dmg::shrink",
+ }
+ )
+
+ key_prefix = {"UDZO": "zlib", "UDBZ": "bzip2", "ULFO": "lzfse", "ULMO": "lzma"}
+ compression_level = options["compression_level"]
+ if options["format"] in key_prefix and compression_level:
+ compression_args = [
+ "-imagekey",
+ key_prefix[options["format"]] + "-level=" + str(compression_level),
+ ]
+ else:
+ compression_args = []
+
+ callback(
+ {
+ "type": "command::start",
+ "command": "hdiutil::convert",
+ }
+ )
+
+ ret, output = hdiutil(
+ "convert",
+ writableFile.name,
+ "-format",
+ options["format"],
+ "-ov",
+ "-o",
+ filename,
+ *compression_args,
+ )
+
+ callback(
+ {
+ "type": "command::finished",
+ "command": "hdiutil::convert",
+ "ret": ret,
+ "output": output,
+ }
+ )
+
+ if ret:
+ raise DMGError(callback, "Unable to convert")
+
+ callback(
+ {
+ "type": "operation::finished",
+ "operation": "dmg::shrink",
+ }
+ )
+
+ if options["license"]:
+ callback(
+ {
+ "type": "operation::start",
+ "command": "dmg::addlicense",
+ }
+ )
+
+ licenseDict = licensing.build_license(options["license"])
+
+ tempLicenseFile = open(os.path.join(dirname, "license.plist"), "wb")
+ plistlib.dump(licenseDict, tempLicenseFile)
+ tempLicenseFile.close()
+
+ # see https://developer.apple.com/forums/thread/668084
+ ret, output = hdiutil(
+ "udifrez", "-xml", tempLicenseFile.name, "", "-quiet", filename, plist=False
+ )
+
+ os.remove(tempLicenseFile.name)
+
+ if ret:
+ raise DMGError(callback, "Unable to add license")
+
+ callback(
+ {
+ "type": "operation::finished",
+ "command": "dmg::addlicense",
+ }
+ )
+
+ callback({"type": "build::finished"})
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/licensing.py b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/licensing.py
new file mode 100644
index 000000000..fb4254ee3
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/licensing.py
@@ -0,0 +1,329 @@
+import os
+
+# based on https://github.com/argv-minus-one/dmg-license/blob/master/language-info-generator/Languages.tsv
+# note that this table specifies STR# Resource ID but it seems to have no effect
+language_info_map = {
+ "da_DK": {"buttons": "Danish", "name": "Danish", "language_id": 9},
+ "de_AT": {"buttons": "German", "name": "German (Austrian)", "language_id": 92},
+ "de_CH": {"buttons": "German", "name": "German (Swiss)", "language_id": 19},
+ "de_DE": {"buttons": "German", "name": "German", "language_id": 3},
+ "en_AU": {"buttons": "English", "name": "English (Australian)", "language_id": 15},
+ "en_GB": {"buttons": "English", "name": "English (UK)", "language_id": 2},
+ "en_IE": {"buttons": "English", "name": "English (Ireland)", "language_id": 108},
+ "en_SG": {
+ "buttons": "English",
+ "name": "English (Singaporean)",
+ "language_id": 100,
+ },
+ "en_US": {"buttons": "English", "name": "English", "language_id": 0},
+ "es_ES": {"buttons": "Spanish", "name": "Spanish", "language_id": 8},
+ "fi_FI": {"buttons": "Finnish", "name": "Finnish", "language_id": 17},
+ "fr_BE": {"buttons": "French", "name": "French (Belgian)", "language_id": 98},
+ "fr_CA": {"buttons": "French", "name": "French (Canadian)", "language_id": 11},
+ "fr_CH": {"buttons": "French", "name": "French (Swiss)", "language_id": 18},
+ "fr_FR": {"buttons": "French", "name": "French", "language_id": 1},
+ "it_IT": {"buttons": "Italian", "name": "Italian", "language_id": 4},
+ "ja_JP": {
+ "buttons": "Japanese",
+ "name": "Japanese",
+ "language_id": 14,
+ "encoding": "shift_jis", # not sure if this is correct encoding, but seems to be working
+ "multibyte": True,
+ },
+ "ko_KR": {
+ "buttons": "Korean",
+ "name": "Korean",
+ "language_id": 51,
+ "encoding": "ksx1001",
+ "multibyte": True,
+ },
+ "nb_NO": {"buttons": "Norwegian", "name": "Norwegian", "language_id": 12},
+ "nl_BE": {"buttons": "Dutch", "name": "Dutch", "language_id": 6},
+ "nl_NL": {"buttons": "Dutch", "name": "Dutch", "language_id": 5},
+ "pt_BR": {
+ "buttons": "Portuguese",
+ "name": "Portuguese (Brazilian)",
+ "language_id": 71,
+ },
+ "pt_PT": {"buttons": "Portuguese", "name": "Portuguese", "language_id": 10},
+ "ru_RU": {
+ "buttons": "Russian",
+ "name": "Russian",
+ "language_id": 49,
+ "encoding": "mac_cyrillic",
+ },
+ "sv_SE": {"buttons": "Swedish", "name": "Swedish", "language_id": 7},
+ "zh_CN": {
+ "buttons": "Simplified",
+ "name": "Simplified Chinese",
+ "language_id": 52,
+ "encoding": "gb2312",
+ "multibyte": True,
+ },
+ "zh_TW": {
+ "buttons": "Traditional",
+ "name": "Traditional Chinese",
+ "language_id": 53,
+ "encoding": "big5",
+ "multibyte": True,
+ },
+}
+
+# Buttons (these come from the SLAResources file which you can find in the SLA
+# SDK on developer.apple.com)
+default_buttons = {
+ "English": (
+ "English",
+ "Agree",
+ "Disagree",
+ "Print",
+ "Save",
+ 'If you agree with the terms of this license, press "Agree" to install the software. If you do not agree, press "Disagree".', # noqa; E501
+ ),
+ "German": (
+ "Deutsch",
+ "Akzeptieren",
+ "Ablehnen",
+ "Drucken",
+ "Sichern...",
+ 'Klicken Sie in "Akzeptieren", wenn Sie mit den Bestimmungen des Software-Lizenzvertrags einverstanden sind. Falls nicht, bitte "Ablehnen" anklicken. Sie können die Software nur installieren, wenn Sie "Akzeptieren" angeklickt haben.', # noqa; E501
+ ),
+ "Spanish": (
+ "Español",
+ "Aceptar",
+ "No aceptar",
+ "Imprimir",
+ "Guardar...",
+ 'Si está de acuerdo con los términos de esta licencia, pulse "Aceptar" para instalar el software. En el supuesto de que no esté de acuerdo con los términos de esta licencia, pulse "No aceptar."', # noqa; E501
+ ),
+ "French": (
+ "Français",
+ "Accepter",
+ "Refuser",
+ "Imprimer",
+ "Enregistrer...",
+ 'Si vous acceptez les termes de la présente licence, cliquez sur "Accepter" afin d\'installer le logiciel. Si vous n\'êtes pas d\'accord avec les termes de la licence, cliquez sur "Refuser".', # noqa; E501
+ ),
+ "Italian": (
+ "Italiano",
+ "Accetto",
+ "Rifiuto",
+ "Stampa",
+ "Registra...",
+ 'Se accetti le condizioni di questa licenza, fai clic su "Accetto" per installare il software. Altrimenti fai clic su "Rifiuto".', # noqa; E501
+ ),
+ "Japanese": (
+ "日本語",
+ "同意します",
+ "同意しません",
+ "印刷する",
+ "保存...",
+ "本ソフトウエア使用許諾契約の条件に同意される場合には、ソフトウエアをインストールするために「同意します」を押してください。\u3000同意されない場合には、「同意しません」を押してください。", # noqa; E501
+ ),
+ "Dutch": (
+ "Nederlands",
+ "Ja",
+ "Nee",
+ "Print",
+ "Bewaar...",
+ "Indien u akkoord gaat met de voorwaarden van deze licentie, kunt u op 'Ja' klikken om de programmatuur te installeren. Indien u niet akkoord gaat, klikt u op 'Nee'.", # noqa; E501
+ ),
+ "Russian": (
+ "Русский",
+ "Согласен",
+ "Не согласен",
+ "Распечатать",
+ "Сохранить",
+ "Если вы согласны с условиями данной лицензии, нажмите «Согласен», чтобы установить программное обеспечение. Если вы не согласны, нажмите «Не согласен».", # noqa; E501
+ ),
+ "Swedish": (
+ "Svensk",
+ "Godkänns",
+ "Avböjs",
+ "Skriv ut",
+ "Spara...",
+ 'Om Du godkänner licensvillkoren klicka på "Godkänns" för att installera programprodukten. Om Du inte godkänner licensvillkoren, klicka på "Avböjs".', # noqa; E501
+ ),
+ "Portuguese": (
+ "Português",
+ "Concordar",
+ "Discordar",
+ "Imprimir",
+ "Salvar...",
+ 'Se está de acordo com os termos desta licença, pressione "Concordar" para instalar o software. Se não está de acordo, pressione "Discordar".', # noqa; E501
+ ),
+ "Simplified Chinese": (
+ "汉语",
+ "同意",
+ "不同意",
+ "打印",
+ "存储…",
+ "如果您同意本许可协议的条款,请按“同意”来安装此软件。如果您不同意,请按“不同意”。", # noqa; E501
+ ),
+ "Traditional Chinese": (
+ "漢語",
+ "同意",
+ "不同意",
+ "列印",
+ "儲存…",
+ "如果您同意本許可證裡的條款,請按“同意”以安裝軟體。如果不同意,請按“不同意”。", # noqa; E501
+ ),
+ "Danish": (
+ "Dansk",
+ "Enig",
+ "Uenig",
+ "Udskriv",
+ "Arkiver...",
+ "Hvis du accepterer betingelserne i licensaftalen, skal du klikke på “Enig” for at installere softwaren. Klik på “Uenig” for at annullere installeringen.", # noqa; E501
+ ),
+ "Finnish": (
+ "Suomi",
+ "Hyväksyn",
+ "En hyväksy",
+ "Tulosta",
+ "Tallenna…",
+ 'Hyväksy lisenssisopimuksen ehdot osoittamalla "Hyväksy". Jos et hyväksy sopimuksen ehtoja, osoita "En hyväksy".', # noqa; E501
+ ),
+ "Korean": (
+ "한국어",
+ "동의",
+ "동의 안함",
+ "프린트",
+ "저장...",
+ '사용 계약서의 내용에 동의하면, "동의" 단추를 눌러 소프트웨어를 설치하십시오. 동의하지 않는다면, "동의 안함" 단추를 누르십시오.', # noqa; E501
+ ),
+ "Norwegian": (
+ "Norsk",
+ "Enig",
+ "Ikke enig",
+ "Skriv ut",
+ "Arkiver...",
+ 'Hvis De er enig i bestemmelsene i denne lisensavtalen, klikker De på "Enig"-knappen for å installere programvaren. Hvis De ikke er enig, klikker De på "Ikke enig".', # noqa; E501
+ ),
+}
+
+udifrezXMLtemplate = {
+ "STR#": [],
+ # ?? License text would be included in a block like this:
+ # ?? 'TEXT': [
+ # ?? {
+ # ?? 'Attributes': '0x0000',
+ # ?? 'Data': b'license text',
+ # ?? 'ID': '5000',
+ # ?? 'Name': 'English'
+ # ?? }
+ # ?? ],
+ "TMPL": [
+ {
+ "Attributes": "0x0000",
+ "Data": b"\x13Default Language IDDWRD\x05CountOCNT\x04****LSTC\x0bsys lang IDDWRD\x1elocal res ID (offset from 5000DWRD\x102-byte language?DWRD\x04****LSTE", # noqa: E501
+ "ID": "128",
+ "Name": "LPic",
+ }
+ ],
+}
+
+
+# Another implementation in TS:
+# https://github.com/argv-minus-one/dmg-license/blob/4268f2e822944fd670c1e197596396f233d6484e/src/makeLicensePlist.ts
+
+
+def build_license(license_info):
+ """Add a license agreement to the specified disk image file, see
+ https://developer.apple.com/forums/thread/668084."""
+ # Copy the original template
+ xml = dict(udifrezXMLtemplate)
+
+ licenses = license_info.get(
+ "licenses",
+ {"en_US": default_buttons["English"][5].encode("utf-8")},
+ )
+
+ lpic = b""
+ # The first field is the default language ID.
+ lpic += int(5000).to_bytes(2, "big")
+ # The second field is the count of language ID to license resource mappings.
+ lpic += len(licenses.items()).to_bytes(2, "big")
+
+ for language, license_data in licenses.items():
+ if language not in language_info_map:
+ raise Exception(
+ "Unknown language '"
+ + language
+ + "'. Valid languages are: "
+ + ", ".join(sorted(language_info_map.keys()))
+ )
+
+ language_info = language_info_map[language]
+ language_buttons = language_info["buttons"]
+ language_name = language_info["name"]
+ language_id = language_info["language_id"]
+ # for simplicity we use the same id for the resource as system language id + 5000
+ resource_id = language_id + 5000
+ language_encoding = language_info.get("encoding", "mac_roman")
+ multibyte_encoding = language_info.get("multibyte_encoding", False)
+
+ if os.path.isfile(license_data):
+ mode = "rb" if license_data.endswith(".rtf") else "r"
+ with open(license_data, mode=mode) as f:
+ license_data = f.read()
+
+ if isinstance(license_data, bytes):
+ if license_data.startswith(b"{\\rtf1"):
+ licenseDataFormat = "RTF "
+ else:
+ licenseDataFormat = "TEXT"
+ else:
+ licenseDataFormat = "TEXT"
+ license_data = license_data.encode(language_encoding)
+
+ if licenseDataFormat not in xml:
+ xml[licenseDataFormat] = []
+
+ xml[licenseDataFormat].append(
+ {
+ "Attributes": "0x0000",
+ "Data": license_data,
+ "ID": str(resource_id),
+ "Name": f"{language_name} SLA",
+ }
+ )
+
+ language_default_buttons = default_buttons.get(
+ language_buttons, default_buttons["English"]
+ )
+ buttons = license_info.get("buttons", {}).get(
+ language, language_default_buttons
+ )
+
+ assert len(buttons) == 6, "License buttons must have 6 entries."
+
+ buttons = [b.encode(language_encoding) for b in buttons]
+ buttons = [len(b).to_bytes(1, "big") + b for b in buttons]
+ xml["STR#"].append(
+ {
+ "Attributes": "0x0000",
+ # \x06 is apparently the number of buttons which is always 6
+ "Data": b"\x00\x06" + b"".join(buttons),
+ "ID": str(resource_id),
+ "Name": language_name,
+ }
+ )
+
+ # Finally, the list of resource ID mappings:
+ # Mapping field 1: system language ID
+ lpic += language_id.to_bytes(2, "big")
+ # Mapping field 2: local resource ID minus 5000
+ lpic += int(resource_id - 5000).to_bytes(2, "big")
+ # Mapping field 3: 2-byte language?
+ lpic += int(1 if multibyte_encoding else 0).to_bytes(2, "big")
+
+ xml["LPic"] = [
+ {
+ "Attributes": "0x0000",
+ "Data": lpic,
+ "ID": "5000",
+ "Name": "",
+ }
+ ]
+ return xml
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/qt_attribution.json b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/qt_attribution.json
index d9f97ae2c..d9f97ae2c 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/qt_attribution.json
+++ b/src/3rdparty/python/lib/python3.9/site-packages/dmgbuild/qt_attribution.json
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/LICENSE b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/LICENSE
index e91f4eb38..e91f4eb38 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/LICENSE
+++ b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/LICENSE
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/ds_store/__init__.py b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/__init__.py
new file mode 100644
index 000000000..bc8702fab
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/__init__.py
@@ -0,0 +1,5 @@
+from .store import DSStore, DSStoreEntry
+
+__version__ = "1.3.1"
+
+__all__ = ["__version__", "DSStore", "DSStoreEntry"]
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/ds_store/__main__.py b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/__main__.py
new file mode 100644
index 000000000..cf17ab5f6
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/__main__.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# ds_store: Examine .DS_Store files.
+#
+
+import argparse
+import os
+import os.path
+import pprint
+import re
+import sys
+
+from ds_store import DSStore
+from ds_store.buddy import BuddyError
+
+_not_printable_re = re.compile(rb"[\x00-\x1f\x7f-\x9f]")
+
+
+def usage():
+ print(main.__doc__)
+ sys.exit(0)
+
+
+def chunks(iterable, length):
+ for i in range(0, len(iterable), length):
+ yield i, iterable[i : i + length]
+
+
+def pretty(value):
+ if isinstance(value, dict):
+ return f"{{\n {pprint.pformat(value, indent=4)[1:-1]}\n}}"
+ elif isinstance(value, bytearray):
+ lines = ["["]
+ for offset, chunk in chunks(value, 16):
+ printable_chunk = _not_printable_re.sub(b".", chunk).decode("latin-1")
+ hex_line = " ".join([f"{b:02x}" for b in chunk])
+ line = f" {offset:08x} {hex_line:<47s} {printable_chunk}"
+ lines.append(line)
+ lines.append("]")
+ return "\n".join(lines)
+ elif isinstance(value, bytes):
+ return value.decode("latin-1")
+ else:
+ return value
+
+
+def main(argv):
+ """Display the contents of the .DS_Store file at the specified path.
+
+ If you specify just a directory, ds_store will inspect the .DS_Store
+ file in that directory. If no paths are specified, ds_store will
+ display the contents of the .DS_Store file in the current directory.
+ """
+
+ parser = argparse.ArgumentParser(description=main.__doc__)
+ parser.add_argument("paths", nargs="*")
+ args = parser.parse_args(argv)
+
+ if len(args.paths) == 0:
+ args.paths = ["."]
+
+ failed = False
+ for path in args.paths:
+ if os.path.isdir(path):
+ path = os.path.join(path, ".DS_Store")
+
+ if not os.path.exists(path) or not os.path.isfile(path):
+ print(f"ds_store: {path} not found", file=sys.stderr)
+ failed = True
+ continue
+
+ try:
+ with DSStore.open(path, "r") as d:
+ print(path)
+ print("")
+
+ max_name_len = 0
+ for entry in d:
+ name_len = len(entry.filename)
+ if name_len > max_name_len:
+ max_name_len = name_len
+
+ for entry in d:
+ print(
+ "{:<{width}} {} {}".format(
+ entry.filename,
+ entry.code.decode("latin-1"),
+ pretty(entry.value),
+ width=max_name_len,
+ )
+ )
+ print("")
+ except BuddyError as e:
+ print(f"ds_store: {path}: {e}")
+ failed = True
+
+ if failed:
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/buddy.py b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/buddy.py
index 320768cd3..27b11b3f7 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/buddy.py
+++ b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/buddy.py
@@ -1,23 +1,14 @@
-# -*- coding: utf-8 -*-
-import os
+import binascii
import bisect
+import os
import struct
-import binascii
-try:
- {}.iterkeys
- iterkeys = lambda x: x.iterkeys()
-except AttributeError:
- iterkeys = lambda x: x.keys()
-try:
- unicode
-except NameError:
- unicode = str
class BuddyError(Exception):
pass
-class Block(object):
+
+class Block:
def __init__(self, allocator, offset, size):
self._allocator = allocator
self._offset = offset
@@ -25,7 +16,7 @@ class Block(object):
self._value = bytearray(allocator.read(offset, size))
self._pos = 0
self._dirty = False
-
+
def __len__(self):
return self._size
@@ -34,7 +25,7 @@ class Block(object):
def __exit__(self, exc_type, exc_value, traceback):
self.close()
-
+
def close(self):
if self._dirty:
self.flush()
@@ -46,13 +37,13 @@ class Block(object):
def invalidate(self):
self._dirty = False
-
+
def zero_fill(self):
len = self._size - self._pos
- zeroes = b'\0' * len
- self._value[self._pos:self._size] = zeroes
+ zeroes = b"\0" * len
+ self._value[self._pos : self._size] = zeroes
self._dirty = True
-
+
def tell(self):
return self._pos
@@ -63,12 +54,12 @@ class Block(object):
pos = self._size - pos
if pos < 0 or pos > self._size:
- raise ValueError('Seek out of range in Block instance')
+ raise ValueError("Seek out of range in Block instance")
self._pos = pos
def read(self, size_or_format):
- if isinstance(size_or_format, (str, unicode, bytes)):
+ if isinstance(size_or_format, (str, bytes)):
size = struct.calcsize(size_or_format)
fmt = size_or_format
else:
@@ -76,11 +67,11 @@ class Block(object):
fmt = None
if self._size - self._pos < size:
- raise BuddyError('Unable to read %lu bytes in block' % size)
+ raise BuddyError("Unable to read %lu bytes in block" % size)
- data = self._value[self._pos:self._pos + size]
+ data = self._value[self._pos : self._pos + size]
self._pos += size
-
+
if fmt is not None:
if isinstance(data, bytearray):
return struct.unpack_from(fmt, bytes(data))
@@ -96,11 +87,11 @@ class Block(object):
data = data_or_format
if self._pos + len(data) > self._size:
- raise ValueError('Attempt to write past end of Block')
+ raise ValueError("Attempt to write past end of Block")
- self._value[self._pos:self._pos + len(data)] = data
+ self._value[self._pos : self._pos + len(data)] = data
self._pos += len(data)
-
+
self._dirty = True
def insert(self, data_or_format, *args):
@@ -109,78 +100,80 @@ class Block(object):
else:
data = data_or_format
- del self._value[-len(data):]
- self._value[self._pos:self._pos] = data
+ del self._value[-len(data) :]
+ self._value[self._pos : self._pos] = data
self._pos += len(data)
self._dirty = True
def delete(self, size):
if self._pos + size > self._size:
- raise ValueError('Attempt to delete past end of Block')
- del self._value[self._pos:self._pos + size]
- self._value += b'\0' * size
+ raise ValueError("Attempt to delete past end of Block")
+ del self._value[self._pos : self._pos + size]
+ self._value += b"\0" * size
self._dirty = True
-
+
def __str__(self):
return binascii.b2a_hex(self._value)
-
-class Allocator(object):
+
+
+class Allocator:
def __init__(self, the_file):
self._file = the_file
self._dirty = False
self._file.seek(0)
-
+
# Read the header
- magic1, magic2, offset, size, offset2, self._unknown1 \
- = self.read(-4, '>I4sIII16s')
+ (magic1, magic2, offset, size, offset2, self._unknown1) = self.read(
+ -4, ">I4sIII16s"
+ )
- if magic2 != b'Bud1' or magic1 != 1:
- raise BuddyError('Not a buddy file')
+ if magic2 != b"Bud1" or magic1 != 1:
+ raise BuddyError("Not a buddy file")
if offset != offset2:
- raise BuddyError('Root addresses differ')
+ raise BuddyError("Root addresses differ")
self._root = Block(self, offset, size)
# Read the block offsets
- count, self._unknown2 = self._root.read('>II')
+ count, self._unknown2 = self._root.read(">II")
self._offsets = []
c = (count + 255) & ~255
while c:
- self._offsets += self._root.read('>256I')
+ self._offsets += self._root.read(">256I")
c -= 256
self._offsets = self._offsets[:count]
-
+
# Read the TOC
self._toc = {}
- count = self._root.read('>I')[0]
+ count = self._root.read(">I")[0]
for n in range(count):
- nlen = self._root.read('B')[0]
+ nlen = self._root.read("B")[0]
name = bytes(self._root.read(nlen))
- value = self._root.read('>I')[0]
+ value = self._root.read(">I")[0]
self._toc[name] = value
# Read the free lists
self._free = []
for n in range(32):
- count = self._root.read('>I')
- self._free.append(list(self._root.read('>%uI' % count)))
-
+ count = self._root.read(">I")
+ self._free.append(list(self._root.read(">%uI" % count)))
+
@classmethod
- def open(cls, file_or_name, mode='r+'):
- if isinstance(file_or_name, (str, unicode)):
- if not 'b' in mode:
- mode = mode[:1] + 'b' + mode[1:]
+ def open(cls, file_or_name, mode="r+"):
+ if isinstance(file_or_name, str):
+ if "b" not in mode:
+ mode = mode[:1] + "b" + mode[1:]
f = open(file_or_name, mode)
else:
f = file_or_name
- if 'w' in mode:
+ if "w" in mode:
# Create an empty file in this case
f.truncate()
-
+
# An empty root block needs 1264 bytes:
#
# 0 4 offset count
@@ -190,7 +183,7 @@ class Allocator(object):
# 1032 4 toc count (0)
# 1036 228 free list
# total 1264
-
+
# The free list will contain the following:
#
# 0 5 * 4 no blocks of width less than 5
@@ -208,28 +201,38 @@ class Allocator(object):
# located at offset 2**n where n is its width.)
# Write the header
- header = struct.pack(b'>I4sIII16s',
- 1, b'Bud1',
- 2048, 1264, 2048,
- b'\x00\x00\x10\x0c'
- b'\x00\x00\x00\x87'
- b'\x00\x00\x20\x0b'
- b'\x00\x00\x00\x00')
+ header = struct.pack(
+ b">I4sIII16s",
+ 1,
+ b"Bud1",
+ 2048,
+ 1264,
+ 2048,
+ b"\x00\x00\x10\x0c"
+ b"\x00\x00\x00\x87"
+ b"\x00\x00\x20\x0b"
+ b"\x00\x00\x00\x00",
+ )
f.write(header)
- f.write(b'\0' * 2016)
-
+ f.write(b"\0" * 2016)
+
# Write the root block
- free_list = [struct.pack(b'>5I', 0, 0, 0, 0, 0)]
+ free_list = [struct.pack(b">5I", 0, 0, 0, 0, 0)]
for n in range(5, 11):
- free_list.append(struct.pack(b'>II', 1, 2**n))
- free_list.append(struct.pack(b'>I', 0))
+ free_list.append(struct.pack(b">II", 1, 2**n))
+ free_list.append(struct.pack(b">I", 0))
for n in range(12, 31):
- free_list.append(struct.pack(b'>II', 1, 2**n))
- free_list.append(struct.pack(b'>I', 0))
-
- root = b''.join([struct.pack(b'>III', 1, 0, 2048 | 5),
- struct.pack(b'>I', 0) * 255,
- struct.pack(b'>I', 0)] + free_list)
+ free_list.append(struct.pack(b">II", 1, 2**n))
+ free_list.append(struct.pack(b">I", 0))
+
+ root = b"".join(
+ [
+ struct.pack(b">III", 1, 0, 2048 | 5),
+ struct.pack(b">I", 0) * 255,
+ struct.pack(b">I", 0),
+ ]
+ + free_list
+ )
f.write(root)
return Allocator(f)
@@ -239,7 +242,7 @@ class Allocator(object):
def __exit__(self, exc_type, exc_value, traceback):
self.close()
-
+
def close(self):
self.flush()
self._file.close()
@@ -252,51 +255,57 @@ class Allocator(object):
self._write_root_block_into(rblk)
addr = self._offsets[0]
- offset = addr & ~0x1f
- size = 1 << (addr & 0x1f)
+ offset = addr & ~0x1F
+ size = 1 << (addr & 0x1F)
self._file.seek(0, os.SEEK_SET)
- self._file.write(struct.pack(b'>I4sIII16s',
- 1, b'Bud1',
- offset, size, offset,
- self._unknown1))
+ self._file.write(
+ struct.pack(
+ b">I4sIII16s", 1, b"Bud1", offset, size, offset, self._unknown1
+ )
+ )
self._dirty = False
-
+
self._file.flush()
-
+
def read(self, offset, size_or_format):
- """Read data at `offset', or raise an exception. `size_or_format'
- may either be a byte count, in which case we return raw data,
- or a format string for `struct.unpack', in which case we
- work out the size and unpack the data before returning it."""
+ """Read data at `offset', or raise an exception.
+
+ `size_or_format' may either be a byte count, in which case we
+ return raw data, or a format string for `struct.unpack', in
+ which case we work out the size and unpack the data before
+ returning it.
+ """
# N.B. There is a fixed offset of four bytes(!)
self._file.seek(offset + 4, os.SEEK_SET)
- if isinstance(size_or_format, (str, unicode)):
+ if isinstance(size_or_format, str):
size = struct.calcsize(size_or_format)
fmt = size_or_format
else:
size = size_or_format
fmt = None
-
+
ret = self._file.read(size)
if len(ret) < size:
- ret += b'\0' * (size - len(ret))
+ ret += b"\0" * (size - len(ret))
if fmt is not None:
if isinstance(ret, bytearray):
ret = struct.unpack_from(fmt, bytes(ret))
else:
ret = struct.unpack(fmt, ret)
-
+
return ret
def write(self, offset, data_or_format, *args):
- """Write data at `offset', or raise an exception. `data_or_format'
- may either be the data to write, or a format string for `struct.pack',
- in which case we pack the additional arguments and write the
- resulting data."""
+ """Write data at `offset', or raise an exception.
+
+ `data_or_format' may either be the data to write, or a format
+ string for `struct.pack', in which case we pack the additional
+ arguments and write the resulting data.
+ """
# N.B. There is a fixed offset of four bytes(!)
self._file.seek(offset + 4, os.SEEK_SET)
@@ -313,11 +322,11 @@ class Allocator(object):
except IndexError:
return None
- offset = addr & ~0x1f
- size = 1 << (addr & 0x1f)
+ offset = addr & ~0x1F
+ size = 1 << (addr & 0x1F)
return Block(self, offset, size)
-
+
def _root_block_size(self):
"""Return the number of bytes required by the root block."""
# Offsets
@@ -330,62 +339,62 @@ class Allocator(object):
# Free list
size += sum([4 + 4 * len(fl) for fl in self._free])
-
+
return size
def _write_root_block_into(self, block):
# Offsets
- block.write('>II', len(self._offsets), self._unknown2)
- block.write('>%uI' % len(self._offsets), *self._offsets)
+ block.write(">II", len(self._offsets), self._unknown2)
+ block.write(">%uI" % len(self._offsets), *self._offsets)
extra = len(self._offsets) & 255
if extra:
- block.write(b'\0\0\0\0' * (256 - extra))
+ block.write(b"\0\0\0\0" * (256 - extra))
# TOC
keys = list(self._toc.keys())
keys.sort()
- block.write('>I', len(keys))
+ block.write(">I", len(keys))
for k in keys:
- block.write('B', len(k))
+ block.write("B", len(k))
block.write(k)
- block.write('>I', self._toc[k])
+ block.write(">I", self._toc[k])
# Free list
for w, f in enumerate(self._free):
- block.write('>I', len(f))
+ block.write(">I", len(f))
if len(f):
- block.write('>%uI' % len(f), *f)
+ block.write(">%uI" % len(f), *f)
def _buddy(self, offset, width):
f = self._free[width]
b = offset ^ (1 << width)
-
+
try:
ndx = f.index(b)
except ValueError:
ndx = None
-
+
return (f, b, ndx)
def _release(self, offset, width):
# Coalesce
while True:
- f,b,ndx = self._buddy(offset, width)
+ f, b, ndx = self._buddy(offset, width)
if ndx is None:
break
-
+
offset &= b
width += 1
del f[ndx]
-
+
# Add to the list
bisect.insort(f, offset)
# Mark as dirty
self._dirty = True
-
+
def _alloc(self, width):
w = width
while not self._free[w]:
@@ -407,15 +416,15 @@ class Allocator(object):
except ValueError:
block = len(self._offsets)
self._offsets.append(0)
-
+
# Compute block width
width = max(bytes.bit_length(), 5)
addr = self._offsets[block]
- offset = addr & ~0x1f
-
+ offset = addr & ~0x1F
+
if addr:
- blkwidth = addr & 0x1f
+ blkwidth = addr & 0x1F
if blkwidth == width:
return block
self._release(offset, width)
@@ -424,13 +433,13 @@ class Allocator(object):
offset = self._alloc(width)
self._offsets[block] = offset | width
return block
-
+
def release(self, block):
addr = self._offsets[block]
if addr:
- width = addr & 0x1f
- offset = addr & ~0x1f
+ width = addr & 0x1F
+ offset = addr & ~0x1F
self._release(offset, width)
if block == len(self._offsets):
@@ -442,37 +451,36 @@ class Allocator(object):
return len(self._toc)
def __getitem__(self, key):
- if not isinstance(key, (str, unicode)):
- raise TypeError('Keys must be of string type')
+ if not isinstance(key, str):
+ raise TypeError("Keys must be of string type")
if not isinstance(key, bytes):
- key = key.encode('latin_1')
+ key = key.encode("latin_1")
return self._toc[key]
def __setitem__(self, key, value):
- if not isinstance(key, (str, unicode)):
- raise TypeError('Keys must be of string type')
+ if not isinstance(key, str):
+ raise TypeError("Keys must be of string type")
if not isinstance(key, bytes):
- key = key.encode('latin_1')
+ key = key.encode("latin_1")
self._toc[key] = value
self._dirty = True
def __delitem__(self, key):
- if not isinstance(key, (str, unicode)):
- raise TypeError('Keys must be of string type')
+ if not isinstance(key, str):
+ raise TypeError("Keys must be of string type")
if not isinstance(key, bytes):
- key = key.encode('latin_1')
+ key = key.encode("latin_1")
del self._toc[key]
self._dirty = True
def iterkeys(self):
- return iterkeys(self._toc)
+ return self._toc.keys()
def keys(self):
- return iterkeys(self._toc)
+ return self._toc.keys()
def __iter__(self):
- return iterkeys(self._toc)
+ return self._toc.keys()
def __contains__(self, key):
return key in self._toc
-
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/qt_attribution.json b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/qt_attribution.json
index a4854d1ed..a4854d1ed 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/qt_attribution.json
+++ b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/qt_attribution.json
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/store.py b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/store.py
index b6f805b24..58e9e1bce 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/store.py
+++ b/src/3rdparty/python/lib/python3.9/site-packages/ds_store/store.py
@@ -1,48 +1,36 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-from __future__ import print_function
-from __future__ import division
-
-import binascii
+import plistlib
import struct
-import biplist
-import mac_alias
-try:
- next
-except NameError:
- next = lambda x: x.next()
-try:
- unicode
-except NameError:
- unicode = str
+import mac_alias
from . import buddy
-class ILocCodec(object):
+
+class ILocCodec:
@staticmethod
def encode(point):
- return struct.pack(b'>IIII', point[0], point[1],
- 0xffffffff, 0xffff0000)
+ return struct.pack(b">IIII", point[0], point[1], 0xFFFFFFFF, 0xFFFF0000)
@staticmethod
def decode(bytesData):
if isinstance(bytesData, bytearray):
- x, y = struct.unpack_from(b'>II', bytes(bytesData[:8]))
+ x, y = struct.unpack_from(b">II", bytes(bytesData[:8]))
else:
- x, y = struct.unpack(b'>II', bytesData[:8])
+ x, y = struct.unpack(b">II", bytesData[:8])
return (x, y)
-class PlistCodec(object):
+
+class PlistCodec:
@staticmethod
def encode(plist):
- return biplist.writePlistToString(plist)
+ return plistlib.dumps(plist, fmt=plistlib.FMT_BINARY)
@staticmethod
def decode(bytes):
- return biplist.readPlistFromString(bytes)
+ return plistlib.loads(bytes)
-class BookmarkCodec(object):
+
+class BookmarkCodec:
@staticmethod
def encode(bmk):
return bmk.to_bytes()
@@ -51,70 +39,73 @@ class BookmarkCodec(object):
def decode(bytes):
return mac_alias.Bookmark.from_bytes(bytes)
+
# This list tells the code how to decode particular kinds of entry in the
# .DS_Store file. This is really a convenience, and we currently only
# support a tiny subset of the possible entry types.
codecs = {
- b'Iloc': ILocCodec,
- b'bwsp': PlistCodec,
- b'lsvp': PlistCodec,
- b'lsvP': PlistCodec,
- b'icvp': PlistCodec,
- b'pBBk': BookmarkCodec
- }
-
-class DSStoreEntry(object):
+ b"Iloc": ILocCodec,
+ b"bwsp": PlistCodec,
+ b"lsvp": PlistCodec,
+ b"lsvP": PlistCodec,
+ b"icvp": PlistCodec,
+ b"pBBk": BookmarkCodec,
+}
+
+
+class DSStoreEntry:
"""Holds the data from an entry in a ``.DS_Store`` file. Note that this is
- not meant to represent the entry itself---i.e. if you change the type
- or value, your changes will *not* be reflected in the underlying file.
+ not meant to represent the entry itself---i.e. if you change the type or
+ value, your changes will *not* be reflected in the underlying file.
- If you want to make a change, you should either use the :class:`DSStore`
- object's :meth:`DSStore.insert` method (which will replace a key if it
- already exists), or the mapping access mode for :class:`DSStore` (often
- simpler anyway).
+ If you want to make a change, you should either use the
+ :class:`DSStore` object's :meth:`DSStore.insert` method (which will
+ replace a key if it already exists), or the mapping access mode for
+ :class:`DSStore` (often simpler anyway).
"""
+
def __init__(self, filename, code, typecode, value=None):
if str != bytes and type(filename) == bytes:
- filename = filename.decode('utf-8')
+ filename = filename.decode("utf-8")
if not isinstance(code, bytes):
- code = code.encode('latin_1')
+ code = code.encode("latin_1")
self.filename = filename
self.code = code
self.type = typecode
self.value = value
-
+
@classmethod
def read(cls, block):
- """Read a ``.DS_Store`` entry from the containing Block"""
+ """Read a ``.DS_Store`` entry from the containing Block."""
# First read the filename
- nlen = block.read(b'>I')[0]
- filename = block.read(2 * nlen).decode('utf-16be')
+ nlen = block.read(b">I")[0]
+ filename = block.read(2 * nlen).decode("utf-16be")
# Next, read the code and type
- code, typecode = block.read(b'>4s4s')
+ code, typecode = block.read(b">4s4s")
# Finally, read the data
- if typecode == b'bool':
- value = block.read(b'>?')[0]
- elif typecode == b'long' or typecode == b'shor':
- value = block.read(b'>I')[0]
- elif typecode == b'blob':
- vlen = block.read(b'>I')[0]
+ if typecode == b"bool":
+ value = block.read(b">?")[0]
+ elif typecode == b"long" or typecode == b"shor":
+ value = block.read(b">I")[0]
+ elif typecode == b"blob":
+ vlen = block.read(b">I")[0]
value = block.read(vlen)
codec = codecs.get(code, None)
if codec:
value = codec.decode(value)
typecode = codec
- elif typecode == b'ustr':
- vlen = block.read(b'>I')[0]
- value = block.read(2 * vlen).decode('utf-16be')
- elif typecode == b'type':
- value = block.read(b'>4s')[0]
- elif typecode == b'comp' or typecode == b'dutc':
- value = block.read(b'>Q')[0]
+ elif typecode == b"ustr":
+ vlen = block.read(b">I")[0]
+ value = block.read(2 * vlen).decode("utf-16be")
+ elif typecode == b"type":
+ value = block.read(b">4s")[0]
+ elif typecode == b"comp" or typecode == b"dutc":
+ value = block.read(b">Q")[0]
else:
raise ValueError('Unknown type code "%s"' % typecode)
@@ -122,152 +113,138 @@ class DSStoreEntry(object):
def __lt__(self, other):
if not isinstance(other, DSStoreEntry):
- raise TypeError('Can only compare against other DSStoreEntry objects')
+ raise TypeError("Can only compare against other DSStoreEntry objects")
sfl = self.filename.lower()
ofl = other.filename.lower()
- return (sfl < ofl
- or (self.filename == other.filename
- and self.code < other.code))
+ return sfl < ofl or (self.filename == other.filename and self.code < other.code)
def __le__(self, other):
if not isinstance(other, DSStoreEntry):
- raise TypeError('Can only compare against other DSStoreEntry objects')
+ raise TypeError("Can only compare against other DSStoreEntry objects")
sfl = self.filename.lower()
ofl = other.filename.lower()
- return (sfl < ofl
- or (sfl == ofl
- and self.code <= other.code))
+ return sfl < ofl or (sfl == ofl and self.code <= other.code)
def __eq__(self, other):
if not isinstance(other, DSStoreEntry):
- raise TypeError('Can only compare against other DSStoreEntry objects')
+ raise TypeError("Can only compare against other DSStoreEntry objects")
sfl = self.filename.lower()
ofl = other.filename.lower()
- return (sfl == ofl
- and self.code == other.code)
+ return sfl == ofl and self.code == other.code
def __ne__(self, other):
if not isinstance(other, DSStoreEntry):
- raise TypeError('Can only compare against other DSStoreEntry objects')
+ raise TypeError("Can only compare against other DSStoreEntry objects")
sfl = self.filename.lower()
ofl = other.filename.lower()
- return (sfl != ofl
- or self.code != other.code)
+ return sfl != ofl or self.code != other.code
def __gt__(self, other):
if not isinstance(other, DSStoreEntry):
- raise TypeError('Can only compare against other DSStoreEntry objects')
+ raise TypeError("Can only compare against other DSStoreEntry objects")
sfl = self.filename.lower()
ofl = other.filename.lower()
-
+
selfCode = self.code
if str != bytes and type(selfCode) is bytes:
- selfCode = selfCode.decode('utf-8')
+ selfCode = selfCode.decode("utf-8")
otherCode = other.code
if str != bytes and type(otherCode) is bytes:
- otherCode = otherCode.decode('utf-8')
-
- return (sfl > ofl or (sfl == ofl and selfCode > otherCode))
+ otherCode = otherCode.decode("utf-8")
+
+ return sfl > ofl or (sfl == ofl and selfCode > otherCode)
def __ge__(self, other):
if not isinstance(other, DSStoreEntry):
- raise TypeError('Can only compare against other DSStoreEntry objects')
+ raise TypeError("Can only compare against other DSStoreEntry objects")
sfl = self.filename.lower()
ofl = other.filename.lower()
- return (sfl > ofl
- or (sfl == ofl
- and self.code >= other.code))
+ return sfl > ofl or (sfl == ofl and self.code >= other.code)
- def __cmp__(self, other):
- if not isinstance(other, DSStoreEntry):
- raise TypeError('Can only compare against other DSStoreEntry objects')
- r = cmp(self.filename.lower(), other.filename.lower())
- if r:
- return r
- return cmp(self.code, other.code)
-
def byte_length(self):
- """Compute the length of this entry, in bytes"""
- utf16 = self.filename.encode('utf-16be')
- l = 4 + len(utf16) + 8
+ """Compute the length of this entry, in bytes."""
+ utf16 = self.filename.encode("utf-16be")
+ length = 4 + len(utf16) + 8
- if isinstance(self.type, unicode):
- entry_type = self.type.encode('latin_1')
+ if isinstance(self.type, str):
+ entry_type = self.type.encode("latin_1")
value = self.value
elif isinstance(self.type, (bytes, str)):
entry_type = self.type
value = self.value
else:
- entry_type = b'blob'
+ entry_type = b"blob"
value = self.type.encode(self.value)
-
- if entry_type == b'bool':
- l += 1
- elif entry_type == b'long' or entry_type == b'shor':
- l += 4
- elif entry_type == b'blob':
- l += 4 + len(value)
- elif entry_type == b'ustr':
- utf16 = value.encode('utf-16be')
- l += 4 + len(utf16)
- elif entry_type == b'type':
- l += 4
- elif entry_type == b'comp' or entry_type == b'dutc':
- l += 8
+
+ if entry_type == b"bool":
+ length += 1
+ elif entry_type == b"long" or entry_type == b"shor":
+ length += 4
+ elif entry_type == b"blob":
+ length += 4 + len(value)
+ elif entry_type == b"ustr":
+ utf16 = value.encode("utf-16be")
+ length += 4 + len(utf16)
+ elif entry_type == b"type":
+ length += 4
+ elif entry_type == b"comp" or entry_type == b"dutc":
+ length += 8
else:
raise ValueError('Unknown type code "%s"' % entry_type)
- return l
-
+ return length
+
def write(self, block, insert=False):
- """Write this entry to the specified Block"""
+ """Write this entry to the specified Block."""
if insert:
w = block.insert
else:
w = block.write
- if isinstance(self.type, unicode):
- entry_type = self.type.encode('latin_1')
+ if isinstance(self.type, str):
+ entry_type = self.type.encode("latin_1")
value = self.value
elif isinstance(self.type, (bytes, str)):
entry_type = self.type
value = self.value
else:
- entry_type = b'blob'
+ entry_type = b"blob"
value = self.type.encode(self.value)
- utf16 = self.filename.encode('utf-16be')
- w(b'>I', len(utf16) // 2)
+ utf16 = self.filename.encode("utf-16be")
+ w(b">I", len(utf16) // 2)
w(utf16)
- w(b'>4s4s', self.code, entry_type)
-
- if entry_type == b'bool':
- w(b'>?', value)
- elif entry_type == b'long' or entry_type == b'shor':
- w(b'>I', value)
- elif entry_type == b'blob':
- w(b'>I', len(value))
+ w(b">4s4s", self.code, entry_type)
+
+ if entry_type == b"bool":
+ w(b">?", value)
+ elif entry_type == b"long" or entry_type == b"shor":
+ w(b">I", value)
+ elif entry_type == b"blob":
+ w(b">I", len(value))
w(value)
- elif entry_type == b'ustr':
- utf16 = value.encode('utf-16be')
- w(b'>I', len(utf16) // 2)
+ elif entry_type == b"ustr":
+ utf16 = value.encode("utf-16be")
+ w(b">I", len(utf16) // 2)
w(utf16)
- elif entry_type == b'type':
- if isinstance(value, unicode):
- value = value.encode('latin_1')
- w(b'>4s', value)
- elif entry_type == b'comp' or entry_type == b'dutc':
- w(b'>Q', value)
+ elif entry_type == b"type":
+ if isinstance(value, str):
+ value = value.encode("latin_1")
+ w(b">4s", value)
+ elif entry_type == b"comp" or entry_type == b"dutc":
+ w(b">Q", value)
else:
raise ValueError('Unknown type code "%s"' % entry_type)
-
+
def __repr__(self):
- return '<%s %s>' % (self.filename, self.code)
+ return f"<{self.filename} {self.code}>"
+
+
+class DSStore:
+ """Python interface to a ``.DS_Store`` file. Works by manipulating the
+ file on the disk---so this code will work with ``.DS_Store`` files for.
-class DSStore(object):
- """Python interface to a ``.DS_Store`` file. Works by manipulating the file
- on the disk---so this code will work with ``.DS_Store`` files for *very*
- large directories.
+ *very* large directories.
A :class:`DSStore` object can be used as if it was a mapping, e.g.::
@@ -280,7 +257,7 @@ class DSStore(object):
Currently, we know how to decode "Iloc", "bwsp", "lsvp", "lsvP" and "icvp"
blobs. "Iloc" decodes to an (x, y) tuple, while the others are all decoded
- using ``biplist``.
+ using ``biplist`` or ``plistlib`` depending on Python version.
Assignment also works, e.g.::
@@ -292,38 +269,48 @@ class DSStore(object):
This is usually going to be the most convenient interface, though
occasionally (for instance when creating a new ``.DS_Store`` file) you
- may wish to drop down to using :class:`DSStoreEntry` objects directly."""
+ may wish to drop down to using :class:`DSStoreEntry` objects directly.
+ """
+
def __init__(self, store):
self._store = store
- self._superblk = self._store['DSDB']
+ self._superblk = self._store["DSDB"]
with self._get_block(self._superblk) as s:
- self._rootnode, self._levels, self._records, \
- self._nodes, self._page_size = s.read(b'>IIIII')
+ (
+ self._rootnode,
+ self._levels,
+ self._records,
+ self._nodes,
+ self._page_size,
+ ) = s.read(b">IIIII")
self._min_usage = 2 * self._page_size // 3
self._dirty = False
-
+
@classmethod
- def open(cls, file_or_name, mode='r+', initial_entries=None):
+ def open(cls, file_or_name, mode="r+", initial_entries=None):
"""Open a ``.DS_Store`` file; pass either a Python file object, or a
- filename in the ``file_or_name`` argument and a file access mode in
- the ``mode`` argument. If you are creating a new file using the "w"
- or "w+" modes, you may also specify a list of entries with which
- to initialise the file."""
+ filename in the ``file_or_name`` argument and a file access mode in the
+ ``mode`` argument.
+
+ If you are creating a new file using the "w" or "w+" modes, you
+ may also specify a list of entries with which to initialise the
+ file.
+ """
store = buddy.Allocator.open(file_or_name, mode)
-
- if mode == 'w' or mode == 'w+':
+
+ if mode == "w" or mode == "w+":
superblk = store.allocate(20)
- store['DSDB'] = superblk
+ store["DSDB"] = superblk
page_size = 4096
-
+
if not initial_entries:
root = store.allocate(page_size)
-
+
with store.get_block(root) as rootblk:
rootblk.zero_fill()
with store.get_block(superblk) as s:
- s.write(b'>IIIII', root, 0, 0, 1, page_size)
+ s.write(b">IIIII", root, 0, 0, 1, page_size)
else:
# Make sure they're in sorted order
initial_entries = list(initial_entries)
@@ -357,48 +344,54 @@ class DSStore(object):
if len(nodes) == 1:
break
-
+
current_level = next_level
next_level = []
ptr_size = 4
# Allocate nodes
ptrs = [store.allocate(page_size) for n in range(node_count)]
-
+
# Generate nodes
pointers = []
prev_pointers = None
for level in levels:
ppndx = 0
- lptrs = ptrs[-len(level):]
- del ptrs[-len(level):]
+ lptrs = ptrs[-len(level) :]
+ del ptrs[-len(level) :]
for node in level:
ndx = lptrs.pop(0)
if prev_pointers is None:
with store.get_block(ndx) as block:
- block.write(b'>II', 0, len(node))
+ block.write(b">II", 0, len(node))
for e in node:
e.write(block)
else:
next_node = prev_pointers[ppndx + len(node)]
- node_ptrs = prev_pointers[ppndx:ppndx+len(node)]
-
+ node_ptrs = prev_pointers[ppndx : ppndx + len(node)]
+
with store.get_block(ndx) as block:
- block.write(b'>II', next_node, len(node))
+ block.write(b">II", next_node, len(node))
for ptr, e in zip(node_ptrs, node):
- block.write(b'>I', ptr)
+ block.write(b">I", ptr)
e.write(block)
-
+
pointers.append(ndx)
prev_pointers = pointers
pointers = []
-
+
root = prev_pointers[0]
with store.get_block(superblk) as s:
- s.write(b'>IIIII', root, len(levels), len(initial_entries),
- node_count, page_size)
-
+ s.write(
+ b">IIIII",
+ root,
+ len(levels),
+ len(initial_entries),
+ node_count,
+ page_size,
+ )
+
return DSStore(store)
def _get_block(self, number):
@@ -410,25 +403,31 @@ class DSStore(object):
self._dirty = False
with self._get_block(self._superblk) as s:
- s.write(b'>IIIII', self._rootnode, self._levels, self._records,
- self._nodes, self._page_size)
+ s.write(
+ b">IIIII",
+ self._rootnode,
+ self._levels,
+ self._records,
+ self._nodes,
+ self._page_size,
+ )
self._store.flush()
def close(self):
"""Flush dirty data and close the underlying file."""
self.flush()
self._store.close()
-
+
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
-
+
# Internal B-Tree nodes look like this:
#
# [ next | count | (ptr0 | rec0) | (ptr1 | rec1) ... (ptrN | recN) ]
-
+
# Leaf nodes look like this:
#
# [ 0 | count | rec0 | rec1 ... recN ]
@@ -438,10 +437,10 @@ class DSStore(object):
if node is None:
node = self._rootnode
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
if next_node:
for n in range(count):
- ptr = block.read(b'>I')[0]
+ ptr = block.read(b">I")[0]
for e in self._traverse(ptr):
yield e
e = DSStoreEntry.read(block)
@@ -456,23 +455,30 @@ class DSStore(object):
# Display the data in `node'
def _dump_node(self, node):
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
- print('next: %u\ncount: %u\n' % (next_node, count))
+ next_node, count = block.read(b">II")
+ print("next: %u\ncount: %u\n" % (next_node, count))
for n in range(count):
if next_node:
- ptr = block.read(b'>I')[0]
- print('%8u ' % ptr, end=' ')
+ ptr = block.read(b">I")[0]
+ print("%8u " % ptr, end=" ")
else:
- print(' ', end=' ')
+ print(" ", end=" ")
e = DSStoreEntry.read(block)
- print(e, ' (%u)' % e.byte_length())
- print('used: %u' % block.tell())
+ print(e, " (%u)" % e.byte_length())
+ print("used: %u" % block.tell())
# Display the data in the super block
def _dump_super(self):
- print('root: %u\nlevels: %u\nrecords: %u\nnodes: %u\npage-size: %u' \
- % (self._rootnode, self._levels, self._records,
- self._nodes, self._page_size))
+ print(
+ "root: %u\nlevels: %u\nrecords: %u\nnodes: %u\npage-size: %u"
+ % (
+ self._rootnode,
+ self._levels,
+ self._records,
+ self._nodes,
+ self._page_size,
+ )
+ )
# Splits entries across two blocks, returning one pivot
#
@@ -480,9 +486,9 @@ class DSStore(object):
def _split2(self, blocks, entries, pointers, before, internal):
left_block = blocks[0]
right_block = blocks[1]
-
+
count = len(entries)
-
+
# Find the feasible splits
best_split = None
best_diff = None
@@ -492,7 +498,7 @@ class DSStore(object):
# We can use a *single* node for this
best_split = count
else:
- # Split into two nodes
+ # Split into two nodes
for n in range(1, count - 1):
left_size = 8 + before[n]
right_size = 8 + total - before[n + 1]
@@ -501,7 +507,7 @@ class DSStore(object):
break
if right_size > self._page_size:
continue
-
+
diff = abs(left_size - right_size)
if best_split is None or diff < best_diff:
@@ -510,54 +516,53 @@ class DSStore(object):
if best_split is None:
return None
-
+
# Write the nodes
left_block.seek(0)
if internal:
next_node = pointers[best_split]
else:
next_node = 0
- left_block.write(b'>II', next_node, best_split)
+ left_block.write(b">II", next_node, best_split)
for n in range(best_split):
if internal:
- left_block.write(b'>I', pointers[n])
+ left_block.write(b">I", pointers[n])
entries[n].write(left_block)
left_block.zero_fill()
if best_split == count:
return []
-
+
right_block.seek(0)
if internal:
next_node = pointers[count]
else:
next_node = 0
- right_block.write(b'>II', next_node, count - best_split - 1)
+ right_block.write(b">II", next_node, count - best_split - 1)
for n in range(best_split + 1, count):
if internal:
- right_block.write(b'>I', pointers[n])
+ right_block.write(b">I", pointers[n])
entries[n].write(right_block)
right_block.zero_fill()
-
+
pivot = entries[best_split]
return [pivot]
-
+
def _split(self, node, entry, right_ptr=0):
self._nodes += 1
self._dirty = True
new_right = self._store.allocate(self._page_size)
- with self._get_block(node) as block, \
- self._get_block(new_right) as right_block:
+ with self._get_block(node) as block, self._get_block(new_right) as right_block:
# First, measure and extract all the elements
entry_size = entry.byte_length()
- entry_pos = None
- next_node, count = block.read(b'>II')
+ # ?? entry_pos = None
+ next_node, count = block.read(b">II")
if next_node:
entry_size += 4
pointers = []
@@ -567,11 +572,11 @@ class DSStore(object):
for n in range(count):
pos = block.tell()
if next_node:
- ptr = block.read(b'>I')[0]
+ ptr = block.read(b">I")[0]
pointers.append(ptr)
e = DSStoreEntry.read(block)
if e > entry:
- entry_pos = n
+ # ?? entry_pos = n
entries.append(entry)
pointers.append(right_ptr)
before.append(total)
@@ -583,10 +588,10 @@ class DSStore(object):
if next_node:
pointers.append(next_node)
- pivot = self._split2([block, right_block],
- entries, pointers, before,
- bool(next_node))[0]
-
+ pivot = self._split2(
+ [block, right_block], entries, pointers, before, bool(next_node)
+ )[0]
+
self._records += 1
self._nodes += 1
self._dirty = True
@@ -598,7 +603,7 @@ class DSStore(object):
def _new_root(self, left, pivot, right):
new_root = self._store.allocate(self._page_size)
with self._get_block(new_root) as block:
- block.write(b'>III', right, 1, left)
+ block.write(b">III", right, 1, left)
pivot.write(block)
self._rootnode = new_root
self._levels += 1
@@ -610,21 +615,21 @@ class DSStore(object):
# pointer (inserted to the RIGHT of `entry')
def _insert_inner(self, path, node, entry, right_ptr):
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
insert_pos = None
insert_ndx = None
n = 0
while n < count:
pos = block.tell()
- ptr = block.read(b'>I')[0]
+ ptr = block.read(b">I")[0]
e = DSStoreEntry.read(block)
if e == entry:
if n == count - 1:
right_ptr = next_node
next_node = ptr
- block_seek(pos)
+ block.seek(pos)
else:
- right_ptr = block.read(b'>I')[0]
+ right_ptr = block.read(b">I")[0]
block.seek(pos + 4)
insert_pos = pos
insert_ndx = n
@@ -651,32 +656,32 @@ class DSStore(object):
else:
if insert_ndx == count:
block.seek(insert_pos)
- block.write(b'>I', next_node)
+ block.write(b">I", next_node)
entry.write(block)
next_node = right_ptr
else:
block.seek(insert_pos + 4)
entry.write(block, True)
- block.insert('>I', right_ptr)
+ block.insert(">I", right_ptr)
block.seek(0)
count += 1
- block.write(b'>II', next_node, count)
+ block.write(b">II", next_node, count)
self._records += 1
self._dirty = True
# Insert `entry' into the leaf node `node'
def _insert_leaf(self, path, node, entry):
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
insert_pos = None
- insert_ndx = None
+ # ?? insert_ndx = None
n = 0
while n < count:
pos = block.tell()
e = DSStoreEntry.read(block)
if e == entry:
insert_pos = pos
- insert_ndx = n
+ # ?? insert_ndx = n
block.seek(pos)
block.delete(e.byte_length())
count -= 1
@@ -685,11 +690,11 @@ class DSStore(object):
continue
elif insert_pos is None and e > entry:
insert_pos = pos
- insert_ndx = n
+ # ?? insert_ndx = n
n += 1
if insert_pos is None:
insert_pos = block.tell()
- insert_ndx = count
+ # ?? insert_ndx = count
remaining = self._page_size - block.tell()
if remaining < entry.byte_length():
@@ -703,21 +708,21 @@ class DSStore(object):
entry.write(block, True)
block.seek(0)
count += 1
- block.write(b'>II', next_node, count)
+ block.write(b">II", next_node, count)
self._records += 1
self._dirty = True
def insert(self, entry):
- """Insert ``entry`` (which should be a :class:`DSStoreEntry`)
- into the B-Tree."""
+ """Insert ``entry`` (which should be a :class:`DSStoreEntry`) into the
+ B-Tree."""
path = []
node = self._rootnode
while True:
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
if next_node:
for n in range(count):
- ptr = block.read(b'>I')[0]
+ ptr = block.read(b">I")[0]
e = DSStoreEntry.read(block)
if entry < e:
next_node = ptr
@@ -735,12 +740,12 @@ class DSStore(object):
# Return usage information for the specified `node'
def _block_usage(self, node):
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
for n in range(count):
if next_node:
- ptr = block.read(b'>I')[0]
- e = DSStoreEntry.read(block)
+ block.read(b">I")[0]
+ DSStoreEntry.read(block)
used = block.tell()
@@ -773,14 +778,14 @@ class DSStore(object):
continue
diff = abs(left_size - mid_size) * abs(right_size - mid_size)
-
+
if best_split is None or diff < best_diff:
best_split = (n, m, count)
best_diff = diff
if best_split is None:
return None
-
+
# Write the nodes
prev_split = -1
for block, split in zip(blocks, best_split):
@@ -789,15 +794,15 @@ class DSStore(object):
next_node = pointers[split]
else:
next_node = 0
- block.write(b'>II', next_node, split)
+ block.write(b">II", next_node, split)
for n in range(prev_split + 1, split):
if internal:
- block.write(b'>I', pointers[n])
+ block.write(b">I", pointers[n])
entries[n].write(block)
block.zero_fill()
-
+
prev_split = split
return (entries[best_split[0]], entries[best_split[1]])
@@ -811,13 +816,13 @@ class DSStore(object):
before = []
total = 0
ppivots = pivots + [None]
- for b,p in zip(blocks, ppivots):
+ for b, p in zip(blocks, ppivots):
b.seek(0)
- next_node, count = b.read(b'>II')
+ next_node, count = b.read(b">II")
for n in range(count):
pos = b.tell()
if next_node:
- ptr = b.read(b'>I')[0]
+ ptr = b.read(b">I")[0]
pointers.append(ptr)
e = DSStoreEntry.read(b)
entries.append(e)
@@ -842,11 +847,11 @@ class DSStore(object):
return
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
-
+ next_node, count = block.read(b">II")
+
with self._get_block(path[-1]) as parent:
# Find the left and right siblings and respective pivots
- parent_next, parent_count = parent.read(b'>II')
+ parent_next, parent_count = parent.read(b">II")
left_pos = None
left_node = None
left_pivot = None
@@ -857,7 +862,7 @@ class DSStore(object):
prev_e = prev_ptr = prev_pos = None
for n in range(parent_count):
pos = parent.tell()
- ptr = parent.read(b'>I')[0]
+ ptr = parent.read(b">I")[0]
e = DSStoreEntry.read(parent)
if ptr == node:
@@ -870,7 +875,7 @@ class DSStore(object):
right_node = ptr
right_pos = pos
break
-
+
prev_e = e
prev_ptr = ptr
prev_pos = pos
@@ -884,24 +889,27 @@ class DSStore(object):
right_node = parent_next
right_pos = parent.tell()
- parent_used = parent.tell()
-
+ _ = parent.tell()
+
if left_node and right_node:
- with self._get_block(left_node) as left, \
- self._get_block(right_node) as right:
+ with self._get_block(left_node) as left, self._get_block(
+ right_node
+ ) as right:
blocks = [left, block, right]
pivots = [left_pivot, right_pivot]
-
+
entries, pointers, before = self._extract(blocks, pivots)
# If there's a chance that we could use two pages instead
# of three, go for it
- pivots = self._split2(blocks, entries, pointers,
- before, bool(next_node))
+ pivots = self._split2(
+ blocks, entries, pointers, before, bool(next_node)
+ )
if pivots is None:
ptrs = [left_node, node, right_node]
- pivots = self._split3(blocks, entries, pointers,
- before, bool(next_node))
+ pivots = self._split3(
+ blocks, entries, pointers, before, bool(next_node)
+ )
else:
if pivots:
ptrs = [left_node, node]
@@ -913,7 +921,7 @@ class DSStore(object):
self._store.release(right_node)
self._nodes -= 1
self._dirty = True
-
+
# Remove the pivots from the parent
with self._get_block(path[-1]) as parent:
if right_node == parent_next:
@@ -925,11 +933,11 @@ class DSStore(object):
parent.delete(right_pos - left_pos)
parent.seek(0)
parent_count -= 2
- parent.write(b'>II', parent_next, parent_count)
+ parent.write(b">II", parent_next, parent_count)
self._records -= 2
-
+
# Replace with those in pivots
- for e,rp in zip(pivots, ptrs[1:]):
+ for e, rp in zip(pivots, ptrs[1:]):
self._insert_inner(path[:-1], path[-1], e, rp)
elif left_node:
with self._get_block(left_node) as left:
@@ -938,8 +946,9 @@ class DSStore(object):
entries, pointers, before = self._extract(blocks, pivots)
- pivots = self._split2(blocks, entries, pointers,
- before, bool(next_node))
+ pivots = self._split2(
+ blocks, entries, pointers, before, bool(next_node)
+ )
# Remove the pivot from the parent
with self._get_block(path[-1]) as parent:
@@ -952,7 +961,7 @@ class DSStore(object):
parent.delete(node_pos - left_pos)
parent.seek(0)
parent_count -= 1
- parent.write(b'>II', parent_next, parent_count)
+ parent.write(b">II", parent_next, parent_count)
self._records -= 1
# Replace the pivot
@@ -965,8 +974,9 @@ class DSStore(object):
entries, pointers, before = self._extract(blocks, pivots)
- pivots = self._split2(blocks, entries, pointers,
- before, bool(next_node))
+ pivots = self._split2(
+ blocks, entries, pointers, before, bool(next_node)
+ )
# Remove the pivot from the parent
with self._get_block(path[-1]) as parent:
@@ -979,13 +989,12 @@ class DSStore(object):
parent.delete(right_pos - node_pos)
parent.seek(0)
parent_count -= 1
- parent.write(b'>II', parent_next, parent_count)
+ parent.write(b">II", parent_next, parent_count)
self._records -= 1
# Replace the pivot
if pivots:
- self._insert_inner(path[:-1], path[-1], pivots[0],
- right_node)
+ self._insert_inner(path[:-1], path[-1], pivots[0], right_node)
if not path and not parent_count:
self._store.release(path[-1])
@@ -994,7 +1003,7 @@ class DSStore(object):
self._rootnode = node
else:
count, used = self._block_usage(path[-1])
-
+
if used < self._page_size // 2:
self._rebalance(path[:-1], path[-1])
@@ -1002,31 +1011,32 @@ class DSStore(object):
# lower-cased.
def _delete_leaf(self, node, filename_lc, code):
found = False
-
+
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
for n in range(count):
pos = block.tell()
e = DSStoreEntry.read(block)
- if e.filename.lower() == filename_lc \
- and (code is None or e.code == code):
+ if e.filename.lower() == filename_lc and (
+ code is None or e.code == code
+ ):
block.seek(pos)
block.delete(e.byte_length())
found = True
-
+
# This does not affect the loop; THIS IS NOT A BUG
count -= 1
self._records -= 1
self._dirty = True
-
+
if found:
used = block.tell()
-
+
block.seek(0)
- block.write(b'>II', next_node, count)
-
+ block.write(b">II", next_node, count)
+
return used < self._page_size // 2
else:
return False
@@ -1040,7 +1050,7 @@ class DSStore(object):
rebalance = None
while True:
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
if next_node:
path.append(node)
@@ -1053,7 +1063,7 @@ class DSStore(object):
count -= 1
block.seek(0)
- block.write(b'>II', next_node, count)
+ block.write(b">II", next_node, count)
if pos < self._page_size // 2:
rebalance = (path, node)
@@ -1064,16 +1074,17 @@ class DSStore(object):
# Delete an entry from an inner node, `node'
def _delete_inner(self, path, node, filename_lc, code):
rebalance = False
-
+
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
for n in range(count):
pos = block.tell()
- ptr = block.read(b'>I')[0]
+ ptr = block.read(b">I")[0]
e = DSStoreEntry.read(block)
- if e.filename.lower() == filename_lc \
- and (code is None or e.code == code):
+ if e.filename.lower() == filename_lc and (
+ code is None or e.code == code
+ ):
# Take the largest from the left subtree
rebalance, largest = self._take_largest(path, ptr)
@@ -1083,20 +1094,20 @@ class DSStore(object):
next_node = ptr
block.seek(pos)
else:
- right_ptr = block.read(b'>I')[0]
+ right_ptr = block.read(b">I")[0]
block.seek(pos + 4)
-
+
block.delete(e.byte_length() + 4)
count -= 1
block.seek(0)
- block.write(b'>II', next_node, count)
+ block.write(b">II", next_node, count)
self._records -= 1
self._dirty = True
-
+
break
-
+
# Replace the pivot value
self._insert_inner(path, node, largest, right_ptr)
@@ -1107,16 +1118,16 @@ class DSStore(object):
return False
def delete(self, filename, code):
- """Delete an item, identified by ``filename`` and ``code``
- from the B-Tree."""
+ """Delete an item, identified by ``filename`` and ``code`` from the
+ B-Tree."""
if isinstance(filename, DSStoreEntry):
code = filename.code
filename = filename.filename
# If we're deleting *every* node for "filename", we must recurse
if code is None:
- ###TODO: Fix this so we can do bulk deletes
- raise ValueError('You must delete items individually. Sorry')
+ # TODO: Fix this so we can do bulk deletes
+ raise ValueError("You must delete items individually. Sorry")
# Otherwise, we're deleting *one* specific node
filename_lc = filename.lower()
@@ -1124,14 +1135,15 @@ class DSStore(object):
node = self._rootnode
while True:
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
if next_node:
for n in range(count):
- ptr = block.read(b'>I')[0]
+ ptr = block.read(b">I")[0]
e = DSStoreEntry.read(block)
e_lc = e.filename.lower()
- if filename_lc < e_lc \
- or (filename_lc == e_lc and code < e.code):
+ if filename_lc < e_lc or (
+ filename_lc == e_lc and code < e.code
+ ):
next_node = ptr
break
elif filename_lc == e_lc and code == e.code:
@@ -1146,13 +1158,13 @@ class DSStore(object):
# Find implementation
def _find(self, node, filename_lc, code=None):
- if not isinstance(code, bytes):
- code = code.encode('latin_1')
+ if code is not None and not isinstance(code, bytes):
+ code = code.encode("latin_1")
with self._get_block(node) as block:
- next_node, count = block.read(b'>II')
+ next_node, count = block.read(b">II")
if next_node:
for n in range(count):
- ptr = block.read(b'>I')[0]
+ ptr = block.read(b">I")[0]
e = DSStoreEntry.read(block)
if filename_lc < e.filename.lower():
for e in self._find(ptr, filename_lc, code):
@@ -1176,16 +1188,16 @@ class DSStore(object):
yield e
elif code < e.code:
return
-
+
def find(self, filename, code=None):
- """Returns a generator that will iterate over matching entries in
- the B-Tree."""
+ """Returns a generator that will iterate over matching entries in the
+ B-Tree."""
if isinstance(filename, DSStoreEntry):
code = filename.code
filename = filename.filename
filename_lc = filename.lower()
-
+
return self._find(self._rootnode, filename_lc, code)
def __len__(self):
@@ -1194,36 +1206,36 @@ class DSStore(object):
def __iter__(self):
return self._traverse(self._rootnode)
- class Partial(object):
+ class Partial:
"""This is used to implement indexing."""
+
def __init__(self, store, filename):
self._store = store
self._filename = filename
def __getitem__(self, code):
if code is None:
- raise KeyError('no such key - [%s][None]' % self._filename)
+ raise KeyError("no such key - [%s][None]" % self._filename)
if not isinstance(code, bytes):
- code = code.encode('latin_1')
+ code = code.encode("latin_1")
try:
item = next(self._store.find(self._filename, code))
except StopIteration:
- raise KeyError('no such key - [%s][%s]' % (self._filename,
- code))
+ raise KeyError(f"no such key - [{self._filename}][{code}]")
- if not isinstance(item.type, (bytes, str, unicode)):
+ if not isinstance(item.type, (bytes, str)):
return item.value
-
+
return (item.type, item.value)
-
+
def __setitem__(self, code, value):
if code is None:
- raise KeyError('bad key - [%s][None]' % self._filename)
+ raise KeyError("bad key - [%s][None]" % self._filename)
if not isinstance(code, bytes):
- code = code.encode('latin_1')
+ code = code.encode("latin_1")
codec = codecs.get(code, None)
if codec:
@@ -1232,20 +1244,19 @@ class DSStore(object):
else:
entry_type = value[0]
entry_value = value[1]
-
- self._store.insert(DSStoreEntry(self._filename, code,
- entry_type, entry_value))
+
+ self._store.insert(
+ DSStoreEntry(self._filename, code, entry_type, entry_value)
+ )
def __delitem__(self, code):
if code is None:
- raise KeyError('no such key - [%s][None]' % self._filename)
+ raise KeyError("no such key - [%s][None]" % self._filename)
self._store.delete(self._filename, code)
def __iter__(self):
- for item in self._store.find(self._filename):
- yield item
+ yield from self._store.find(self._filename)
def __getitem__(self, filename):
return self.Partial(self, filename)
-
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/LICENSE b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/LICENSE
index e91f4eb38..e91f4eb38 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/LICENSE
+++ b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/LICENSE
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/__init__.py b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/__init__.py
new file mode 100644
index 000000000..0f925e7d1
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/__init__.py
@@ -0,0 +1,47 @@
+# flake8: noqa
+from .alias import *
+from .bookmark import *
+
+__version__ = "2.2.2"
+
+__all__ = [
+ "__version__",
+ "ALIAS_KIND_FILE",
+ "ALIAS_KIND_FOLDER",
+ "ALIAS_HFS_VOLUME_SIGNATURE",
+ "ALIAS_FIXED_DISK",
+ "ALIAS_NETWORK_DISK",
+ "ALIAS_400KB_FLOPPY_DISK",
+ "ALIAS_800KB_FLOPPY_DISK",
+ "ALIAS_1_44MB_FLOPPY_DISK",
+ "ALIAS_EJECTABLE_DISK",
+ "ALIAS_NO_CNID",
+ "kBookmarkPath",
+ "kBookmarkCNIDPath",
+ "kBookmarkFileProperties",
+ "kBookmarkFileName",
+ "kBookmarkFileID",
+ "kBookmarkFileCreationDate",
+ "kBookmarkTOCPath",
+ "kBookmarkVolumePath",
+ "kBookmarkVolumeURL",
+ "kBookmarkVolumeName",
+ "kBookmarkVolumeUUID",
+ "kBookmarkVolumeSize",
+ "kBookmarkVolumeCreationDate",
+ "kBookmarkVolumeProperties",
+ "kBookmarkContainingFolder",
+ "kBookmarkUserName",
+ "kBookmarkUID",
+ "kBookmarkWasFileReference",
+ "kBookmarkCreationOptions",
+ "kBookmarkURLLengths",
+ "kBookmarkSecurityExtension",
+ "AppleShareInfo",
+ "VolumeInfo",
+ "TargetInfo",
+ "Alias",
+ "Bookmark",
+ "Data",
+ "URL",
+]
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/alias.py b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/alias.py
index f74b949ea..bf34fb01b 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/alias.py
+++ b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/alias.py
@@ -1,30 +1,29 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-from __future__ import division
-
-import struct
import datetime
import io
-import re
import os
import os.path
-import stat
+import struct
import sys
+from unicodedata import normalize
-if sys.platform == 'darwin':
+if sys.platform == "darwin":
from . import osx
-try:
- long
-except NameError:
- long = int
+from .utils import mac_epoch
-from .utils import *
-
ALIAS_KIND_FILE = 0
ALIAS_KIND_FOLDER = 1
-ALIAS_HFS_VOLUME_SIGNATURE = b'H+'
+ALIAS_HFS_VOLUME_SIGNATURE = b"H+"
+
+ALIAS_FILESYSTEM_UDF = "UDF (CD/DVD)"
+ALIAS_FILESYSTEM_FAT32 = "FAT32"
+ALIAS_FILESYSTEM_EXFAT = "exFAT"
+ALIAS_FILESYSTEM_HFSX = "HFSX"
+ALIAS_FILESYSTEM_HFSPLUS = "HFS+"
+ALIAS_FILESYSTEM_FTP = "FTP"
+ALIAS_FILESYSTEM_NTFS = "NTFS"
+ALIAS_FILESYSTEM_UNKNOWN = "unknown"
ALIAS_FIXED_DISK = 0
ALIAS_NETWORK_DISK = 1
@@ -33,19 +32,36 @@ ALIAS_800KB_FLOPPY_DISK = 3
ALIAS_1_44MB_FLOPPY_DISK = 4
ALIAS_EJECTABLE_DISK = 5
-ALIAS_NO_CNID = 0xffffffff
+ALIAS_NO_CNID = 0xFFFFFFFF
+
+ALIAS_FSTYPE_MAP = {
+ # Version 2 aliases
+ b"HX": ALIAS_FILESYSTEM_HFSX,
+ b"H+": ALIAS_FILESYSTEM_HFSPLUS,
+ # Version 3 aliases
+ b"BDcu": ALIAS_FILESYSTEM_UDF,
+ b"BDIS": ALIAS_FILESYSTEM_FAT32,
+ b"BDxF": ALIAS_FILESYSTEM_EXFAT,
+ b"HX\0\0": ALIAS_FILESYSTEM_HFSX,
+ b"H+\0\0": ALIAS_FILESYSTEM_HFSPLUS,
+ b"KG\0\0": ALIAS_FILESYSTEM_FTP,
+ b"NTcu": ALIAS_FILESYSTEM_NTFS,
+}
+
def encode_utf8(s):
if isinstance(s, bytes):
return s
- return s.encode('utf-8')
+ return s.encode("utf-8")
+
def decode_utf8(s):
if isinstance(s, bytes):
- return s.decode('utf-8')
+ return s.decode("utf-8")
return s
-class AppleShareInfo (object):
+
+class AppleShareInfo:
def __init__(self, zone=None, server=None, user=None):
#: The AppleShare zone
self.zone = zone
@@ -55,22 +71,38 @@ class AppleShareInfo (object):
self.user = user
def __repr__(self):
- return 'AppleShareInfo(%r,%r,%r)' % (self.zone, self.server, self.user)
-
-class VolumeInfo (object):
- def __init__(self, name, creation_date, fs_type, disk_type,
- attribute_flags, fs_id, appleshare_info=None,
- driver_name=None, posix_path=None, disk_image_alias=None,
- dialup_info=None, network_mount_info=None):
+ return "AppleShareInfo({!r},{!r},{!r})".format(
+ self.zone, self.server, self.user
+ )
+
+
+class VolumeInfo:
+ def __init__(
+ self,
+ name,
+ creation_date,
+ fs_type,
+ disk_type,
+ attribute_flags,
+ fs_id,
+ appleshare_info=None,
+ driver_name=None,
+ posix_path=None,
+ disk_image_alias=None,
+ dialup_info=None,
+ network_mount_info=None,
+ ):
#: The name of the volume on which the target resides
self.name = name
-
+
#: The creation date of the target's volume
self.creation_date = creation_date
-
- #: The filesystem type (a two character code, e.g. ``b'H+'`` for HFS+)
+
+ #: The filesystem type
+ #: (for v2 aliases, this is a 2-character code; for v3 aliases, a
+ #: 4-character code).
self.fs_type = fs_type
-
+
#: The type of disk; should be one of
#:
#: * ALIAS_FIXED_DISK
@@ -109,30 +141,60 @@ class VolumeInfo (object):
#: Network mount information (for automatic remounting)
self.network_mount_info = network_mount_info
+ @property
+ def filesystem_type(self):
+ return ALIAS_FSTYPE_MAP.get(self.fs_type, ALIAS_FILESYSTEM_UNKNOWN)
+
def __repr__(self):
- args = ['name', 'creation_date', 'fs_type', 'disk_type',
- 'attribute_flags', 'fs_id']
+ args = [
+ "name",
+ "creation_date",
+ "fs_type",
+ "disk_type",
+ "attribute_flags",
+ "fs_id",
+ ]
values = []
for a in args:
v = getattr(self, a)
values.append(repr(v))
-
- kwargs = ['appleshare_info', 'driver_name', 'posix_path',
- 'disk_image_alias', 'dialup_info', 'network_mount_info']
+
+ kwargs = [
+ "appleshare_info",
+ "driver_name",
+ "posix_path",
+ "disk_image_alias",
+ "dialup_info",
+ "network_mount_info",
+ ]
for a in kwargs:
v = getattr(self, a)
if v is not None:
- values.append('%s=%r' % (a, v))
- return 'VolumeInfo(%s)' % ','.join(values)
-
-class TargetInfo (object):
- def __init__(self, kind, filename, folder_cnid, cnid, creation_date,
- creator_code, type_code, levels_from=-1, levels_to=-1,
- folder_name=None, cnid_path=None, carbon_path=None,
- posix_path=None, user_home_prefix_len=None):
+ values.append(f"{a}={v!r}")
+ return "VolumeInfo(%s)" % ",".join(values)
+
+
+class TargetInfo:
+ def __init__(
+ self,
+ kind,
+ filename,
+ folder_cnid,
+ cnid,
+ creation_date,
+ creator_code,
+ type_code,
+ levels_from=-1,
+ levels_to=-1,
+ folder_name=None,
+ cnid_path=None,
+ carbon_path=None,
+ posix_path=None,
+ user_home_prefix_len=None,
+ ):
#: Either ALIAS_KIND_FILE or ALIAS_KIND_FOLDER
self.kind = kind
-
+
#: The filename of the target
self.filename = filename
@@ -178,26 +240,39 @@ class TargetInfo (object):
self.user_home_prefix_len = user_home_prefix_len
def __repr__(self):
- args = ['kind', 'filename', 'folder_cnid', 'cnid', 'creation_date',
- 'creator_code', 'type_code']
+ args = [
+ "kind",
+ "filename",
+ "folder_cnid",
+ "cnid",
+ "creation_date",
+ "creator_code",
+ "type_code",
+ ]
values = []
for a in args:
v = getattr(self, a)
values.append(repr(v))
if self.levels_from != -1:
- values.append('levels_from=%r' % self.levels_from)
+ values.append("levels_from=%r" % self.levels_from)
if self.levels_to != -1:
- values.append('levels_to=%r' % self.levels_to)
-
- kwargs = ['folder_name', 'cnid_path', 'carbon_path',
- 'posix_path', 'user_home_prefix_len']
+ values.append("levels_to=%r" % self.levels_to)
+
+ kwargs = [
+ "folder_name",
+ "cnid_path",
+ "carbon_path",
+ "posix_path",
+ "user_home_prefix_len",
+ ]
for a in kwargs:
v = getattr(self, a)
- values.append('%s=%r' % (a, v))
+ values.append(f"{a}={v!r}")
+
+ return "TargetInfo(%s)" % ",".join(values)
+
- return 'TargetInfo(%s)' % ','.join(values)
-
TAG_CARBON_FOLDER_NAME = 0
TAG_CNID_PATH = 1
TAG_CARBON_PATH = 2
@@ -216,21 +291,28 @@ TAG_POSIX_PATH_TO_MOUNTPOINT = 19
TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE = 20
TAG_USER_HOME_LENGTH_PREFIX = 21
-class Alias (object):
- def __init__(self, appinfo=b'\0\0\0\0', version=2, volume=None,
- target=None, extra=[]):
+
+class Alias:
+ def __init__(
+ self,
+ appinfo=b"\0\0\0\0",
+ version=2,
+ volume=None,
+ target=None,
+ extra=[],
+ ):
"""Construct a new :class:`Alias` object with the specified
contents."""
#: Application specific information (four byte byte-string)
self.appinfo = appinfo
- #: Version (we support only version 2)
+ #: Version (we support versions 2 and 3)
self.version = version
#: A :class:`VolumeInfo` object describing the target's volume
self.volume = volume
-
+
#: A :class:`TargetInfo` object describing the target
self.target = target
@@ -239,47 +321,91 @@ class Alias (object):
@classmethod
def _from_fd(cls, b):
- appinfo, recsize, version = struct.unpack(b'>4shh', b.read(8))
+ appinfo, recsize, version = struct.unpack(b">4shh", b.read(8))
if recsize < 150:
- raise ValueError('Incorrect alias length')
-
- if version != 2:
- raise ValueError('Unsupported alias version %u' % version)
-
- kind, volname, voldate, fstype, disktype, \
- folder_cnid, filename, cnid, crdate, creator_code, type_code, \
- levels_from, levels_to, volattrs, volfsid, reserved = \
- struct.unpack(b'>h28pI2shI64pII4s4shhI2s10s', b.read(142))
+ raise ValueError("Incorrect alias length")
+
+ if version not in (2, 3):
+ raise ValueError("Unsupported alias version %u" % version)
+
+ if version == 2:
+ (
+ kind, # h
+ volname, # 28p
+ voldate, # I
+ fstype, # 2s
+ disktype, # h
+ folder_cnid, # I
+ filename, # 64p
+ cnid, # I
+ crdate, # I
+ creator_code, # 4s
+ type_code, # 4s
+ levels_from, # h
+ levels_to, # h
+ volattrs, # I
+ volfsid, # 2s
+ reserved, # 10s
+ ) = struct.unpack(b">h28pI2shI64pII4s4shhI2s10s", b.read(142))
+ else:
+ (
+ kind, # h
+ voldate_hr, # Q
+ fstype, # 4s
+ disktype, # h
+ folder_cnid, # I
+ cnid, # I
+ crdate_hr, # Q
+ volattrs, # I
+ reserved, # 14s
+ ) = struct.unpack(b">hQ4shIIQI14s", b.read(46))
+
+ volname = b""
+ filename = b""
+ creator_code = None
+ type_code = None
+ voldate = voldate_hr / 65536.0
+ crdate = crdate_hr / 65536.0
voldate = mac_epoch + datetime.timedelta(seconds=voldate)
crdate = mac_epoch + datetime.timedelta(seconds=crdate)
alias = Alias()
alias.appinfo = appinfo
-
- alias.volume = VolumeInfo (volname.decode().replace('/',':'),
- voldate, fstype, disktype,
- volattrs, volfsid)
- alias.target = TargetInfo (kind, filename.decode().replace('/',':'),
- folder_cnid, cnid,
- crdate, creator_code, type_code)
+
+ alias.volume = VolumeInfo(
+ volname.decode().replace("/", ":"),
+ voldate,
+ fstype,
+ disktype,
+ volattrs,
+ volfsid,
+ )
+ alias.target = TargetInfo(
+ kind,
+ filename.decode().replace("/", ":"),
+ folder_cnid,
+ cnid,
+ crdate,
+ creator_code,
+ type_code,
+ )
alias.target.levels_from = levels_from
alias.target.levels_to = levels_to
-
- tag = struct.unpack(b'>h', b.read(2))[0]
+
+ tag = struct.unpack(b">h", b.read(2))[0]
while tag != -1:
- length = struct.unpack(b'>h', b.read(2))[0]
+ length = struct.unpack(b">h", b.read(2))[0]
value = b.read(length)
if length & 1:
b.read(1)
if tag == TAG_CARBON_FOLDER_NAME:
- alias.target.folder_name = value.decode().replace('/',':')
+ alias.target.folder_name = value.decode().replace("/", ":")
elif tag == TAG_CNID_PATH:
- alias.target.cnid_path = struct.unpack('>%uI' % (length // 4),
- value)
+ alias.target.cnid_path = struct.unpack(">%uI" % (length // 4), value)
elif tag == TAG_CARBON_PATH:
alias.target.carbon_path = value
elif tag == TAG_APPLESHARE_ZONE:
@@ -301,17 +427,19 @@ class Alias (object):
elif tag == TAG_DIALUP_INFO:
alias.volume.dialup_info = value
elif tag == TAG_UNICODE_FILENAME:
- alias.target.filename = value[2:].decode('utf-16be')
+ alias.target.filename = value[2:].decode("utf-16be")
elif tag == TAG_UNICODE_VOLUME_NAME:
- alias.volume.name = value[2:].decode('utf-16be')
+ alias.volume.name = value[2:].decode("utf-16be")
elif tag == TAG_HIGH_RES_VOLUME_CREATION_DATE:
- seconds = struct.unpack(b'>Q', value)[0] / 65536.0
- alias.volume.creation_date \
- = mac_epoch + datetime.timedelta(seconds=seconds)
+ seconds = struct.unpack(b">Q", value)[0] / 65536.0
+ alias.volume.creation_date = mac_epoch + datetime.timedelta(
+ seconds=seconds
+ )
elif tag == TAG_HIGH_RES_CREATION_DATE:
- seconds = struct.unpack(b'>Q', value)[0] / 65536.0
- alias.target.creation_date \
- = mac_epoch + datetime.timedelta(seconds=seconds)
+ seconds = struct.unpack(b">Q", value)[0] / 65536.0
+ alias.target.creation_date = mac_epoch + datetime.timedelta(
+ seconds=seconds
+ )
elif tag == TAG_POSIX_PATH:
alias.target.posix_path = value.decode()
elif tag == TAG_POSIX_PATH_TO_MOUNTPOINT:
@@ -319,14 +447,14 @@ class Alias (object):
elif tag == TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE:
alias.volume.disk_image_alias = Alias.from_bytes(value)
elif tag == TAG_USER_HOME_LENGTH_PREFIX:
- alias.target.user_home_prefix_len = struct.unpack(b'>h', value)[0]
+ alias.target.user_home_prefix_len = struct.unpack(b">h", value)[0]
else:
alias.extra.append((tag, value))
- tag = struct.unpack(b'>h', b.read(2))[0]
-
+ tag = struct.unpack(b">h", b.read(2))[0]
+
return alias
-
+
@classmethod
def from_bytes(cls, bytes):
"""Construct an :class:`Alias` object given binary Alias data."""
@@ -336,32 +464,40 @@ class Alias (object):
@classmethod
def for_file(cls, path):
"""Create an :class:`Alias` that points at the specified file."""
- if sys.platform != 'darwin':
- raise Exception('Not implemented (requires special support)')
+ if sys.platform != "darwin":
+ raise Exception("Not implemented (requires special support)")
path = encode_utf8(path)
-
+
a = Alias()
# Find the filesystem
st = osx.statfs(path)
vol_path = st.f_mntonname
-
+
+ # File and folder names in HFS+ are normalized to a form similar to NFD.
+ # Must be normalized (NFD->NFC) before use to avoid unicode string comparison issues.
+ vol_path = normalize("NFC", vol_path.decode("utf-8")).encode("utf-8")
+
# Grab its attributes
- attrs = [osx.ATTR_CMN_CRTIME,
- osx.ATTR_VOL_NAME,
- 0, 0, 0]
+ attrs = [osx.ATTR_CMN_CRTIME, osx.ATTR_VOL_NAME, 0, 0, 0]
volinfo = osx.getattrlist(vol_path, attrs, 0)
vol_crtime = volinfo[0]
vol_name = encode_utf8(volinfo[1])
-
+
# Also grab various attributes of the file
- attrs = [(osx.ATTR_CMN_OBJTYPE
- | osx.ATTR_CMN_CRTIME
- | osx.ATTR_CMN_FNDRINFO
- | osx.ATTR_CMN_FILEID
- | osx.ATTR_CMN_PARENTID), 0, 0, 0, 0]
+ attrs = [
+ osx.ATTR_CMN_OBJTYPE
+ | osx.ATTR_CMN_CRTIME
+ | osx.ATTR_CMN_FNDRINFO
+ | osx.ATTR_CMN_FILEID
+ | osx.ATTR_CMN_PARENTID,
+ 0,
+ 0,
+ 0,
+ 0,
+ ]
info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
if info[0] == osx.VDIR:
@@ -371,27 +507,27 @@ class Alias (object):
cnid = info[3]
folder_cnid = info[4]
-
+
dirname, filename = os.path.split(path)
- if dirname == b'' or dirname == b'.':
+ if dirname == b"" or dirname == b".":
dirname = os.getcwd()
foldername = os.path.basename(dirname)
-
+
creation_date = info[1]
if kind == ALIAS_KIND_FILE:
- creator_code = struct.pack(b'I', info[2].fileInfo.fileCreator)
- type_code = struct.pack(b'I', info[2].fileInfo.fileType)
+ creator_code = struct.pack(b"I", info[2].fileInfo.fileCreator)
+ type_code = struct.pack(b"I", info[2].fileInfo.fileType)
else:
- creator_code = b'\0\0\0\0'
- type_code = b'\0\0\0\0'
+ creator_code = b"\0\0\0\0"
+ type_code = b"\0\0\0\0"
- a.target = TargetInfo(kind, filename, folder_cnid, cnid, creation_date,
- creator_code, type_code)
- a.volume = VolumeInfo(vol_name, vol_crtime, b'H+',
- ALIAS_FIXED_DISK, 0, b'\0\0')
+ a.target = TargetInfo(
+ kind, filename, folder_cnid, cnid, creation_date, creator_code, type_code
+ )
+ a.volume = VolumeInfo(vol_name, vol_crtime, b"H+", ALIAS_FIXED_DISK, 0, b"\0\0")
a.target.folder_name = foldername
a.volume.posix_path = vol_path
@@ -399,10 +535,10 @@ class Alias (object):
rel_path = os.path.relpath(path, vol_path)
# Leave off the initial '/' if vol_path is '/' (no idea why)
- if vol_path == b'/':
+ if vol_path == b"/":
a.target.posix_path = rel_path
else:
- a.target.posix_path = b'/' + rel_path
+ a.target.posix_path = b"/" + rel_path
# Construct the Carbon and CNID paths
carbon_path = []
@@ -415,173 +551,202 @@ class Alias (object):
attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
cnid_path.append(info[0])
- carbon_tail = tail.replace(b':',b'/')
+ carbon_tail = tail.replace(b":", b"/")
carbon_path.insert(0, carbon_tail)
head, tail = os.path.split(head)
- carbon_path = vol_name + b':' + b':\0'.join(carbon_path)
+ carbon_path = vol_name + b":" + b":\0".join(carbon_path)
a.target.carbon_path = carbon_path
a.target.cnid_path = cnid_path
return a
-
+
def _to_fd(self, b):
# We'll come back and fix the length when we're done
pos = b.tell()
- b.write(struct.pack(b'>4shh', self.appinfo, 0, self.version))
+ b.write(struct.pack(b">4shh", self.appinfo, 0, self.version))
- carbon_volname = encode_utf8(self.volume.name).replace(b':',b'/')
- carbon_filename = encode_utf8(self.target.filename).replace(b':',b'/')
+ carbon_volname = encode_utf8(self.volume.name).replace(b":", b"/")
+ carbon_filename = encode_utf8(self.target.filename).replace(b":", b"/")
voldate = (self.volume.creation_date - mac_epoch).total_seconds()
crdate = (self.target.creation_date - mac_epoch).total_seconds()
- # NOTE: crdate should be in local time, but that's system dependent
- # (so doing so is ridiculous, and nothing could rely on it).
- b.write(struct.pack(b'>h28pI2shI64pII4s4shhI2s10s',
- self.target.kind,
- carbon_volname, int(voldate),
- self.volume.fs_type,
- self.volume.disk_type,
- self.target.folder_cnid,
- carbon_filename,
- self.target.cnid,
- int(crdate),
- self.target.creator_code,
- self.target.type_code,
- self.target.levels_from,
- self.target.levels_to,
- self.volume.attribute_flags,
- self.volume.fs_id,
- b'\0'*10))
+ if self.version == 2:
+ # NOTE: crdate should be in local time, but that's system dependent
+ # (so doing so is ridiculous, and nothing could rely on it).
+ b.write(
+ struct.pack(
+ b">h28pI2shI64pII4s4shhI2s10s",
+ self.target.kind, # h
+ carbon_volname, # 28p
+ int(voldate), # I
+ self.volume.fs_type, # 2s
+ self.volume.disk_type, # h
+ self.target.folder_cnid, # I
+ carbon_filename, # 64p
+ self.target.cnid, # I
+ int(crdate), # I
+ self.target.creator_code, # 4s
+ self.target.type_code, # 4s
+ self.target.levels_from, # h
+ self.target.levels_to, # h
+ self.volume.attribute_flags, # I
+ self.volume.fs_id, # 2s
+ b"\0" * 10, # 10s
+ )
+ )
+ else:
+ b.write(
+ struct.pack(
+ b">hQ4shIIQI14s",
+ self.target.kind, # h
+ int(voldate * 65536), # Q
+ self.volume.fs_type, # 4s
+ self.volume.disk_type, # h
+ self.target.folder_cnid, # I
+ self.target.cnid, # I
+ int(crdate * 65536), # Q
+ self.volume.attribute_flags, # I
+ b"\0" * 14, # 14s
+ )
+ )
# Excuse the odd order; we're copying Finder
if self.target.folder_name:
- carbon_foldername = encode_utf8(self.target.folder_name)\
- .replace(b':',b'/')
- b.write(struct.pack(b'>hh', TAG_CARBON_FOLDER_NAME,
- len(carbon_foldername)))
+ carbon_foldername = encode_utf8(self.target.folder_name).replace(b":", b"/")
+ b.write(struct.pack(b">hh", TAG_CARBON_FOLDER_NAME, len(carbon_foldername)))
b.write(carbon_foldername)
if len(carbon_foldername) & 1:
- b.write(b'\0')
+ b.write(b"\0")
- b.write(struct.pack(b'>hhQhhQ',
+ b.write(
+ struct.pack(
+ b">hhQhhQ",
TAG_HIGH_RES_VOLUME_CREATION_DATE,
- 8, int(voldate * 65536),
+ 8,
+ int(voldate * 65536),
TAG_HIGH_RES_CREATION_DATE,
- 8, int(crdate * 65536)))
+ 8,
+ int(crdate * 65536),
+ )
+ )
if self.target.cnid_path:
- cnid_path = struct.pack('>%uI' % len(self.target.cnid_path),
- *self.target.cnid_path)
- b.write(struct.pack(b'>hh', TAG_CNID_PATH,
- len(cnid_path)))
+ cnid_path = struct.pack(
+ ">%uI" % len(self.target.cnid_path), *self.target.cnid_path
+ )
+ b.write(struct.pack(b">hh", TAG_CNID_PATH, len(cnid_path)))
b.write(cnid_path)
if self.target.carbon_path:
- carbon_path=encode_utf8(self.target.carbon_path)
- b.write(struct.pack(b'>hh', TAG_CARBON_PATH,
- len(carbon_path)))
+ carbon_path = encode_utf8(self.target.carbon_path)
+ b.write(struct.pack(b">hh", TAG_CARBON_PATH, len(carbon_path)))
b.write(carbon_path)
if len(carbon_path) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if self.volume.appleshare_info:
ai = self.volume.appleshare_info
if ai.zone:
- b.write(struct.pack(b'>hh', TAG_APPLESHARE_ZONE,
- len(ai.zone)))
+ b.write(struct.pack(b">hh", TAG_APPLESHARE_ZONE, len(ai.zone)))
b.write(ai.zone)
if len(ai.zone) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if ai.server:
- b.write(struct.pack(b'>hh', TAG_APPLESHARE_SERVER_NAME,
- len(ai.server)))
+ b.write(struct.pack(b">hh", TAG_APPLESHARE_SERVER_NAME, len(ai.server)))
b.write(ai.server)
if len(ai.server) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if ai.username:
- b.write(struct.pack(b'>hh', TAG_APPLESHARE_USERNAME,
- len(ai.username)))
+ b.write(struct.pack(b">hh", TAG_APPLESHARE_USERNAME, len(ai.username)))
b.write(ai.username)
if len(ai.username) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if self.volume.driver_name:
driver_name = encode_utf8(self.volume.driver_name)
- b.write(struct.pack(b'>hh', TAG_DRIVER_NAME,
- len(driver_name)))
+ b.write(struct.pack(b">hh", TAG_DRIVER_NAME, len(driver_name)))
b.write(driver_name)
if len(driver_name) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if self.volume.network_mount_info:
- b.write(struct.pack(b'>hh', TAG_NETWORK_MOUNT_INFO,
- len(self.volume.network_mount_info)))
+ b.write(
+ struct.pack(
+ b">hh", TAG_NETWORK_MOUNT_INFO, len(self.volume.network_mount_info)
+ )
+ )
b.write(self.volume.network_mount_info)
if len(self.volume.network_mount_info) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if self.volume.dialup_info:
- b.write(struct.pack(b'>hh', TAG_DIALUP_INFO,
- len(self.volume.network_mount_info)))
+ b.write(
+ struct.pack(
+ b">hh", TAG_DIALUP_INFO, len(self.volume.network_mount_info)
+ )
+ )
b.write(self.volume.network_mount_info)
if len(self.volume.network_mount_info) & 1:
- b.write(b'\0')
+ b.write(b"\0")
- utf16 = decode_utf8(self.target.filename)\
- .replace(':','/').encode('utf-16-be')
- b.write(struct.pack(b'>hhh', TAG_UNICODE_FILENAME,
- len(utf16) + 2,
- len(utf16) // 2))
+ utf16 = decode_utf8(self.target.filename).replace(":", "/").encode("utf-16-be")
+ b.write(
+ struct.pack(b">hhh", TAG_UNICODE_FILENAME, len(utf16) + 2, len(utf16) // 2)
+ )
b.write(utf16)
- utf16 = decode_utf8(self.volume.name)\
- .replace(':','/').encode('utf-16-be')
- b.write(struct.pack(b'>hhh', TAG_UNICODE_VOLUME_NAME,
- len(utf16) + 2,
- len(utf16) // 2))
+ utf16 = decode_utf8(self.volume.name).replace(":", "/").encode("utf-16-be")
+ b.write(
+ struct.pack(
+ b">hhh", TAG_UNICODE_VOLUME_NAME, len(utf16) + 2, len(utf16) // 2
+ )
+ )
b.write(utf16)
if self.target.posix_path:
posix_path = encode_utf8(self.target.posix_path)
- b.write(struct.pack(b'>hh', TAG_POSIX_PATH,
- len(posix_path)))
+ b.write(struct.pack(b">hh", TAG_POSIX_PATH, len(posix_path)))
b.write(posix_path)
if len(posix_path) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if self.volume.posix_path:
posix_path = encode_utf8(self.volume.posix_path)
- b.write(struct.pack(b'>hh', TAG_POSIX_PATH_TO_MOUNTPOINT,
- len(posix_path)))
+ b.write(struct.pack(b">hh", TAG_POSIX_PATH_TO_MOUNTPOINT, len(posix_path)))
b.write(posix_path)
if len(posix_path) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if self.volume.disk_image_alias:
d = self.volume.disk_image_alias.to_bytes()
- b.write(struct.pack(b'>hh', TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE,
- len(d)))
+ b.write(struct.pack(b">hh", TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE, len(d)))
b.write(d)
if len(d) & 1:
- b.write(b'\0')
+ b.write(b"\0")
if self.target.user_home_prefix_len is not None:
- b.write(struct.pack(b'>hhh', TAG_USER_HOME_LENGTH_PREFIX,
- 2, self.target.user_home_prefix_len))
-
- for t,v in self.extra:
- b.write(struct.pack(b'>hh', t, len(v)))
+ b.write(
+ struct.pack(
+ b">hhh",
+ TAG_USER_HOME_LENGTH_PREFIX,
+ 2,
+ self.target.user_home_prefix_len,
+ )
+ )
+
+ for t, v in self.extra:
+ b.write(struct.pack(b">hh", t, len(v)))
b.write(v)
if len(v) & 1:
- b.write(b'\0')
+ b.write(b"\0")
+
+ b.write(struct.pack(b">hh", -1, 0))
- b.write(struct.pack(b'>hh', -1, 0))
-
blen = b.tell() - pos
b.seek(pos + 4, os.SEEK_SET)
- b.write(struct.pack(b'>h', blen))
+ b.write(struct.pack(b">h", blen))
def to_bytes(self):
"""Returns the binary representation for this :class:`Alias`."""
@@ -590,18 +755,18 @@ class Alias (object):
return b.getvalue()
def __str__(self):
- return '<Alias target=%s>' % self.target.filename
+ return "<Alias target=%s>" % self.target.filename
def __repr__(self):
values = []
- if self.appinfo != b'\0\0\0\0':
- values.append('appinfo=%r' % self.appinfo)
+ if self.appinfo != b"\0\0\0\0":
+ values.append("appinfo=%r" % self.appinfo)
if self.version != 2:
- values.append('version=%r' % self.version)
+ values.append("version=%r" % self.version)
if self.volume is not None:
- values.append('volume=%r' % self.volume)
+ values.append("volume=%r" % self.volume)
if self.target is not None:
- values.append('target=%r' % self.target)
+ values.append("target=%r" % self.target)
if self.extra:
- values.append('extra=%r' % self.extra)
- return 'Alias(%s)' % ','.join(values)
+ values.append("extra=%r" % self.extra)
+ return "Alias(%s)" % ",".join(values)
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/bookmark.py b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/bookmark.py
new file mode 100644
index 000000000..00e16feb1
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/bookmark.py
@@ -0,0 +1,677 @@
+# This file implements the Apple "bookmark" format, which is the replacement
+# for the old-fashioned alias format. The details of this format were
+# reverse engineered; some things are still not entirely clear.
+#
+import datetime
+import os
+import struct
+import sys
+import uuid
+from urllib.parse import urljoin
+
+if sys.platform == "darwin":
+ from . import osx
+
+from .utils import osx_epoch
+
+BMK_DATA_TYPE_MASK = 0xFFFFFF00
+BMK_DATA_SUBTYPE_MASK = 0x000000FF
+
+BMK_STRING = 0x0100
+BMK_DATA = 0x0200
+BMK_NUMBER = 0x0300
+BMK_DATE = 0x0400
+BMK_BOOLEAN = 0x0500
+BMK_ARRAY = 0x0600
+BMK_DICT = 0x0700
+BMK_UUID = 0x0800
+BMK_URL = 0x0900
+BMK_NULL = 0x0A00
+
+BMK_ST_ZERO = 0x0000
+BMK_ST_ONE = 0x0001
+
+BMK_BOOLEAN_ST_FALSE = 0x0000
+BMK_BOOLEAN_ST_TRUE = 0x0001
+
+# Subtypes for BMK_NUMBER are really CFNumberType values
+kCFNumberSInt8Type = 1
+kCFNumberSInt16Type = 2
+kCFNumberSInt32Type = 3
+kCFNumberSInt64Type = 4
+kCFNumberFloat32Type = 5
+kCFNumberFloat64Type = 6
+kCFNumberCharType = 7
+kCFNumberShortType = 8
+kCFNumberIntType = 9
+kCFNumberLongType = 10
+kCFNumberLongLongType = 11
+kCFNumberFloatType = 12
+kCFNumberDoubleType = 13
+kCFNumberCFIndexType = 14
+kCFNumberNSIntegerType = 15
+kCFNumberCGFloatType = 16
+
+# Resource property flags (from CFURLPriv.h)
+kCFURLResourceIsRegularFile = 0x00000001
+kCFURLResourceIsDirectory = 0x00000002
+kCFURLResourceIsSymbolicLink = 0x00000004
+kCFURLResourceIsVolume = 0x00000008
+kCFURLResourceIsPackage = 0x00000010
+kCFURLResourceIsSystemImmutable = 0x00000020
+kCFURLResourceIsUserImmutable = 0x00000040
+kCFURLResourceIsHidden = 0x00000080
+kCFURLResourceHasHiddenExtension = 0x00000100
+kCFURLResourceIsApplication = 0x00000200
+kCFURLResourceIsCompressed = 0x00000400
+kCFURLResourceIsSystemCompressed = 0x00000400
+kCFURLCanSetHiddenExtension = 0x00000800
+kCFURLResourceIsReadable = 0x00001000
+kCFURLResourceIsWriteable = 0x00002000
+kCFURLResourceIsExecutable = 0x00004000
+kCFURLIsAliasFile = 0x00008000
+kCFURLIsMountTrigger = 0x00010000
+
+# Volume property flags (from CFURLPriv.h)
+kCFURLVolumeIsLocal = 0x1
+kCFURLVolumeIsAutomount = 0x2
+kCFURLVolumeDontBrowse = 0x4
+kCFURLVolumeIsReadOnly = 0x8
+kCFURLVolumeIsQuarantined = 0x10
+kCFURLVolumeIsEjectable = 0x20
+kCFURLVolumeIsRemovable = 0x40
+kCFURLVolumeIsInternal = 0x80
+kCFURLVolumeIsExternal = 0x100
+kCFURLVolumeIsDiskImage = 0x200
+kCFURLVolumeIsFileVault = 0x400
+kCFURLVolumeIsLocaliDiskMirror = 0x800
+kCFURLVolumeIsiPod = 0x1000
+kCFURLVolumeIsiDisk = 0x2000
+kCFURLVolumeIsCD = 0x4000
+kCFURLVolumeIsDVD = 0x8000
+kCFURLVolumeIsDeviceFileSystem = 0x10000
+kCFURLVolumeSupportsPersistentIDs = 0x100000000
+kCFURLVolumeSupportsSearchFS = 0x200000000
+kCFURLVolumeSupportsExchange = 0x400000000
+# reserved 0x800000000
+kCFURLVolumeSupportsSymbolicLinks = 0x1000000000
+kCFURLVolumeSupportsDenyModes = 0x2000000000
+kCFURLVolumeSupportsCopyFile = 0x4000000000
+kCFURLVolumeSupportsReadDirAttr = 0x8000000000
+kCFURLVolumeSupportsJournaling = 0x10000000000
+kCFURLVolumeSupportsRename = 0x20000000000
+kCFURLVolumeSupportsFastStatFS = 0x40000000000
+kCFURLVolumeSupportsCaseSensitiveNames = 0x80000000000
+kCFURLVolumeSupportsCasePreservedNames = 0x100000000000
+kCFURLVolumeSupportsFLock = 0x200000000000
+kCFURLVolumeHasNoRootDirectoryTimes = 0x400000000000
+kCFURLVolumeSupportsExtendedSecurity = 0x800000000000
+kCFURLVolumeSupports2TBFileSize = 0x1000000000000
+kCFURLVolumeSupportsHardLinks = 0x2000000000000
+kCFURLVolumeSupportsMandatoryByteRangeLocks = 0x4000000000000
+kCFURLVolumeSupportsPathFromID = 0x8000000000000
+# reserved 0x10000000000000
+kCFURLVolumeIsJournaling = 0x20000000000000
+kCFURLVolumeSupportsSparseFiles = 0x40000000000000
+kCFURLVolumeSupportsZeroRuns = 0x80000000000000
+kCFURLVolumeSupportsVolumeSizes = 0x100000000000000
+kCFURLVolumeSupportsRemoteEvents = 0x200000000000000
+kCFURLVolumeSupportsHiddenFiles = 0x400000000000000
+kCFURLVolumeSupportsDecmpFSCompression = 0x800000000000000
+kCFURLVolumeHas64BitObjectIDs = 0x1000000000000000
+kCFURLVolumePropertyFlagsAll = 0xFFFFFFFFFFFFFFFF
+
+BMK_URL_ST_ABSOLUTE = 0x0001
+BMK_URL_ST_RELATIVE = 0x0002
+
+# Bookmark keys
+kBookmarkURL = 0x1003 # A URL
+kBookmarkPath = 0x1004 # Array of path components
+kBookmarkCNIDPath = 0x1005 # Array of CNIDs
+kBookmarkFileProperties = (
+ 0x1010 # (CFURL rp flags, CFURL rp flags asked for, 8 bytes NULL)
+)
+kBookmarkFileName = 0x1020
+kBookmarkFileID = 0x1030
+kBookmarkFileCreationDate = 0x1040
+# = 0x1054 # ?
+# = 0x1055 # ?
+# = 0x1056 # ?
+# = 0x1101 # ?
+# = 0x1102 # ?
+kBookmarkTOCPath = 0x2000 # A list of (TOC id, ?) pairs
+kBookmarkVolumePath = 0x2002
+kBookmarkVolumeURL = 0x2005
+kBookmarkVolumeName = 0x2010
+kBookmarkVolumeUUID = 0x2011 # Stored (perversely) as a string
+kBookmarkVolumeSize = 0x2012
+kBookmarkVolumeCreationDate = 0x2013
+kBookmarkVolumeProperties = (
+ 0x2020 # (CFURL vp flags, CFURL vp flags asked for, 8 bytes NULL)
+)
+kBookmarkVolumeIsRoot = 0x2030 # True if volume is FS root
+kBookmarkVolumeBookmark = 0x2040 # Embedded bookmark for disk image (TOC id)
+kBookmarkVolumeMountPoint = 0x2050 # A URL
+# = 0x2070
+kBookmarkContainingFolder = 0xC001 # Index of containing folder in path
+kBookmarkUserName = 0xC011 # User that created bookmark
+kBookmarkUID = 0xC012 # UID that created bookmark
+kBookmarkWasFileReference = 0xD001 # True if the URL was a file reference
+kBookmarkCreationOptions = 0xD010
+kBookmarkURLLengths = 0xE003 # See below
+kBookmarkDisplayName = 0xF017
+kBookmarkIconData = 0xF020
+kBookmarkIconRef = 0xF021
+kBookmarkTypeBindingData = 0xF022
+kBookmarkCreationTime = 0xF030
+kBookmarkSandboxRwExtension = 0xF080
+kBookmarkSandboxRoExtension = 0xF081
+kBookmarkAliasData = 0xFE00
+
+# Alias for backwards compatibility
+kBookmarkSecurityExtension = kBookmarkSandboxRwExtension
+
+# kBookmarkURLLengths is an array that is set if the URL encoded by the
+# bookmark had a base URL; in that case, each entry is the length of the
+# base URL in question. Thus a URL
+#
+# file:///foo/bar/baz blam/blat.html
+#
+# will result in [3, 2], while the URL
+#
+# file:///foo bar/baz blam blat.html
+#
+# would result in [1, 2, 1, 1]
+
+
+class Data:
+ def __init__(self, bytedata=None):
+ #: The bytes, stored as a byte string
+ self.bytes = bytes(bytedata)
+
+ def __repr__(self):
+ return "Data(%r)" % self.bytes
+
+
+class URL:
+ def __init__(self, base, rel=None):
+ if rel is not None:
+ #: The base URL, if any (a :class:`URL`)
+ self.base = base
+ #: The rest of the URL (a string)
+ self.relative = rel
+ else:
+ self.base = None
+ self.relative = base
+
+ @property
+ def absolute(self):
+ """Return an absolute URL."""
+ if self.base is None:
+ return self.relative
+ else:
+ return urljoin(self.base.absolute, self.relative)
+
+ def __repr__(self):
+ return "URL(%r)" % self.absolute
+
+
+class Bookmark:
+ def __init__(self, tocs=None):
+ if tocs is None:
+ #: The TOCs for this Bookmark
+ self.tocs = []
+ else:
+ self.tocs = tocs
+
+ @classmethod
+ def _get_item(cls, data, hdrsize, offset):
+ offset += hdrsize
+ if offset > len(data) - 8:
+ raise ValueError("Offset out of range")
+
+ length, typecode = struct.unpack(b"<II", data[offset : offset + 8])
+
+ if len(data) - offset < 8 + length:
+ raise ValueError("Data item truncated")
+
+ databytes = data[offset + 8 : offset + 8 + length]
+
+ dsubtype = typecode & BMK_DATA_SUBTYPE_MASK
+ dtype = typecode & BMK_DATA_TYPE_MASK
+
+ if dtype == BMK_STRING:
+ return databytes.decode("utf-8")
+ elif dtype == BMK_DATA:
+ return Data(databytes)
+ elif dtype == BMK_NUMBER:
+ if dsubtype == kCFNumberSInt8Type:
+ return ord(databytes[0])
+ elif dsubtype == kCFNumberSInt16Type:
+ return struct.unpack(b"<h", databytes)[0]
+ elif dsubtype == kCFNumberSInt32Type:
+ return struct.unpack(b"<i", databytes)[0]
+ elif dsubtype == kCFNumberSInt64Type:
+ return struct.unpack(b"<q", databytes)[0]
+ elif dsubtype == kCFNumberFloat32Type:
+ return struct.unpack(b"<f", databytes)[0]
+ elif dsubtype == kCFNumberFloat64Type:
+ return struct.unpack(b"<d", databytes)[0]
+ elif dtype == BMK_DATE:
+ # Yes, dates really are stored as *BIG-endian* doubles; everything
+ # else is little-endian
+ secs = datetime.timedelta(seconds=struct.unpack(b">d", databytes)[0])
+ return osx_epoch + secs
+ elif dtype == BMK_BOOLEAN:
+ if dsubtype == BMK_BOOLEAN_ST_TRUE:
+ return True
+ elif dsubtype == BMK_BOOLEAN_ST_FALSE:
+ return False
+ elif dtype == BMK_UUID:
+ return uuid.UUID(bytes=databytes)
+ elif dtype == BMK_URL:
+ if dsubtype == BMK_URL_ST_ABSOLUTE:
+ return URL(databytes.decode("utf-8"))
+ elif dsubtype == BMK_URL_ST_RELATIVE:
+ baseoff, reloff = struct.unpack(b"<II", databytes)
+ base = cls._get_item(data, hdrsize, baseoff)
+ rel = cls._get_item(data, hdrsize, reloff)
+ return URL(base, rel)
+ elif dtype == BMK_ARRAY:
+ result = []
+ for aoff in range(offset + 8, offset + 8 + length, 4):
+ (eltoff,) = struct.unpack(b"<I", data[aoff : aoff + 4])
+ result.append(cls._get_item(data, hdrsize, eltoff))
+ return result
+ elif dtype == BMK_DICT:
+ result = {}
+ for eoff in range(offset + 8, offset + 8 + length, 8):
+ keyoff, valoff = struct.unpack(b"<II", data[eoff : eoff + 8])
+ key = cls._get_item(data, hdrsize, keyoff)
+ val = cls._get_item(data, hdrsize, valoff)
+ result[key] = val
+ return result
+ elif dtype == BMK_NULL:
+ return None
+
+ print("Unknown data type %08x" % typecode)
+ return (typecode, databytes)
+
+ @classmethod
+ def from_bytes(cls, data):
+ """Create a :class:`Bookmark` given byte data."""
+
+ if len(data) < 16:
+ raise ValueError("Not a bookmark file (too short)")
+
+ if isinstance(data, bytearray):
+ data = bytes(data)
+
+ magic, size, dummy, hdrsize = struct.unpack(b"<4sIII", data[0:16])
+
+ if magic not in (b"book", b"alis"):
+ raise ValueError("Not a bookmark file (bad magic) %r" % magic)
+
+ if hdrsize < 16:
+ raise ValueError("Not a bookmark file (header size too short)")
+
+ if hdrsize > size:
+ raise ValueError("Not a bookmark file (header size too large)")
+
+ if size != len(data):
+ raise ValueError("Not a bookmark file (truncated)")
+
+ (tocoffset,) = struct.unpack(b"<I", data[hdrsize : hdrsize + 4])
+
+ tocs = []
+
+ while tocoffset != 0:
+ tocbase = hdrsize + tocoffset
+ if (tocoffset > size - hdrsize) or (size - tocbase < 20):
+ raise ValueError("TOC offset out of range")
+
+ (tocsize, tocmagic, tocid, nexttoc, toccount) = struct.unpack(
+ b"<IIIII", data[tocbase : tocbase + 20]
+ )
+
+ if tocmagic != 0xFFFFFFFE:
+ break
+
+ tocsize += 8
+
+ if size - tocbase < tocsize:
+ raise ValueError("TOC truncated")
+
+ if tocsize < 12 * toccount:
+ raise ValueError("TOC entries overrun TOC size")
+
+ toc = {}
+ for n in range(0, toccount):
+ ebase = tocbase + 20 + 12 * n
+ eid, eoffset, edummy = struct.unpack(b"<III", data[ebase : ebase + 12])
+
+ if eid & 0x80000000:
+ eid = cls._get_item(data, hdrsize, eid & 0x7FFFFFFF)
+
+ toc[eid] = cls._get_item(data, hdrsize, eoffset)
+
+ tocs.append((tocid, toc))
+
+ tocoffset = nexttoc
+
+ return cls(tocs)
+
+ def __getitem__(self, key):
+ for tid, toc in self.tocs:
+ if key in toc:
+ return toc[key]
+ raise KeyError("Key not found")
+
+ def __setitem__(self, key, value):
+ if len(self.tocs) == 0:
+ self.tocs = [(1, {})]
+ self.tocs[0][1][key] = value
+
+ def get(self, key, default=None):
+ """Lookup the value for a given key, returning a default if not
+ present."""
+ for tid, toc in self.tocs:
+ if key in toc:
+ return toc[key]
+ return default
+
+ @classmethod
+ def _encode_item(cls, item, offset):
+ if item is True:
+ result = struct.pack(b"<II", 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_TRUE)
+ elif item is False:
+ result = struct.pack(b"<II", 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_FALSE)
+ elif isinstance(item, str):
+ encoded = item.encode("utf-8")
+ result = (
+ struct.pack(b"<II", len(encoded), BMK_STRING | BMK_ST_ONE) + encoded
+ )
+ elif isinstance(item, bytes):
+ result = struct.pack(b"<II", len(item), BMK_STRING | BMK_ST_ONE) + item
+ elif isinstance(item, Data):
+ result = struct.pack(
+ b"<II", len(item.bytes), BMK_DATA | BMK_ST_ONE
+ ) + bytes(item.bytes)
+ elif isinstance(item, bytearray):
+ result = struct.pack(b"<II", len(item), BMK_DATA | BMK_ST_ONE) + bytes(item)
+ elif isinstance(item, int):
+ if item > -0x80000000 and item < 0x7FFFFFFF:
+ result = struct.pack(b"<IIi", 4, BMK_NUMBER | kCFNumberSInt32Type, item)
+ else:
+ result = struct.pack(b"<IIq", 8, BMK_NUMBER | kCFNumberSInt64Type, item)
+ elif isinstance(item, float):
+ result = struct.pack(b"<IId", 8, BMK_NUMBER | kCFNumberFloat64Type, item)
+ elif isinstance(item, datetime.datetime):
+ secs = item - osx_epoch
+ result = struct.pack(b"<II", 8, BMK_DATE | BMK_ST_ZERO) + struct.pack(
+ b">d", float(secs.total_seconds())
+ )
+
+ elif isinstance(item, uuid.UUID):
+ result = struct.pack(b"<II", 16, BMK_UUID | BMK_ST_ONE) + item.bytes
+ elif isinstance(item, URL):
+ if item.base:
+ baseoff = offset + 16
+ reloff, baseenc = cls._encode_item(item.base, baseoff)
+ xoffset, relenc = cls._encode_item(item.relative, reloff)
+ result = b"".join(
+ [
+ struct.pack(
+ b"<IIII", 8, BMK_URL | BMK_URL_ST_RELATIVE, baseoff, reloff
+ ),
+ baseenc,
+ relenc,
+ ]
+ )
+ else:
+ encoded = item.relative.encode("utf-8")
+ result = (
+ struct.pack(b"<II", len(encoded), BMK_URL | BMK_URL_ST_ABSOLUTE)
+ + encoded
+ )
+ elif isinstance(item, list):
+ ioffset = offset + 8 + len(item) * 4
+ result = [struct.pack(b"<II", len(item) * 4, BMK_ARRAY | BMK_ST_ONE)]
+ enc = []
+ for elt in item:
+ result.append(struct.pack(b"<I", ioffset))
+ ioffset, ienc = cls._encode_item(elt, ioffset)
+ enc.append(ienc)
+ result = b"".join(result + enc)
+ elif isinstance(item, dict):
+ ioffset = offset + 8 + len(item) * 8
+ result = [struct.pack(b"<II", len(item) * 8, BMK_DICT | BMK_ST_ONE)]
+ enc = []
+ for k, v in item.items():
+ result.append(struct.pack(b"<I", ioffset))
+ ioffset, ienc = cls._encode_item(k, ioffset)
+ enc.append(ienc)
+ result.append(struct.pack(b"<I", ioffset))
+ ioffset, ienc = cls._encode_item(v, ioffset)
+ enc.append(ienc)
+ result = b"".join(result + enc)
+ elif item is None:
+ result = struct.pack(b"<II", 0, BMK_NULL | BMK_ST_ONE)
+ else:
+ raise ValueError("Unknown item type when encoding: %s" % item)
+
+ offset += len(result)
+
+ # Pad to a multiple of 4 bytes
+ if offset & 3:
+ extra = 4 - (offset & 3)
+ result += b"\0" * extra
+ offset += extra
+
+ return (offset, result)
+
+ def to_bytes(self):
+ """Convert this :class:`Bookmark` to a byte representation."""
+
+ result = []
+ tocs = []
+ offset = 4 # For the offset to the first TOC
+
+ # Generate the data and build the TOCs
+ for tid, toc in self.tocs:
+ entries = []
+
+ for k, v in toc.items():
+ if isinstance(k, str):
+ noffset = offset
+ voffset, enc = self._encode_item(k, offset)
+ result.append(enc)
+ offset, enc = self._encode_item(v, voffset)
+ result.append(enc)
+ entries.append((noffset | 0x80000000, voffset))
+ else:
+ entries.append((k, offset))
+ offset, enc = self._encode_item(v, offset)
+ result.append(enc)
+
+ # TOC entries must be sorted - CoreServicesInternal does a
+ # binary search to find data
+ entries.sort()
+
+ tocs.append(
+ (tid, b"".join([struct.pack(b"<III", k, o, 0) for k, o in entries]))
+ )
+
+ first_toc_offset = offset
+
+ # Now generate the TOC headers
+ for ndx, toc in enumerate(tocs):
+ tid, data = toc
+ if ndx == len(tocs) - 1:
+ next_offset = 0
+ else:
+ next_offset = offset + 20 + len(data)
+
+ result.append(
+ struct.pack(
+ b"<IIIII",
+ len(data) - 8,
+ 0xFFFFFFFE,
+ tid,
+ next_offset,
+ len(data) // 12,
+ )
+ )
+ result.append(data)
+
+ offset += 20 + len(data)
+
+ # Finally, add the header (and the first TOC offset, which isn't part
+ # of the header, but goes just after it)
+ header = struct.pack(
+ b"<4sIIIQQQQI",
+ b"book",
+ offset + 48,
+ 0x10040000,
+ 48,
+ 0,
+ 0,
+ 0,
+ 0,
+ first_toc_offset,
+ )
+
+ result.insert(0, header)
+
+ return b"".join(result)
+
+ @classmethod
+ def for_file(cls, path):
+ """Construct a :class:`Bookmark` for a given file."""
+
+ # Find the filesystem
+ st = osx.statfs(path)
+ vol_path = st.f_mntonname.decode("utf-8")
+
+ # Grab its attributes
+ attrs = [
+ osx.ATTR_CMN_CRTIME,
+ osx.ATTR_VOL_SIZE | osx.ATTR_VOL_NAME | osx.ATTR_VOL_UUID,
+ 0,
+ 0,
+ 0,
+ ]
+ volinfo = osx.getattrlist(vol_path, attrs, 0)
+
+ vol_crtime = volinfo[0]
+ vol_size = volinfo[1]
+ vol_name = volinfo[2]
+ vol_uuid = volinfo[3]
+
+ # Also grab various attributes of the file
+ attrs = [
+ (osx.ATTR_CMN_OBJTYPE | osx.ATTR_CMN_CRTIME | osx.ATTR_CMN_FILEID),
+ 0,
+ 0,
+ 0,
+ 0,
+ ]
+ info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
+
+ cnid = info[2]
+ crtime = info[1]
+
+ if info[0] == osx.VREG:
+ flags = kCFURLResourceIsRegularFile
+ elif info[0] == osx.VDIR:
+ flags = kCFURLResourceIsDirectory
+ elif info[0] == osx.VLNK:
+ flags = kCFURLResourceIsSymbolicLink
+ else:
+ flags = kCFURLResourceIsRegularFile
+
+ dirname, filename = os.path.split(path)
+
+ relcount = 0
+ if not os.path.isabs(dirname):
+ curdir = os.getcwd()
+ head, tail = os.path.split(curdir)
+ relcount = 0
+ while head and tail:
+ relcount += 1
+ head, tail = os.path.split(head)
+ dirname = os.path.join(curdir, dirname)
+
+ # ?? foldername = os.path.basename(dirname)
+
+ rel_path = os.path.relpath(path, vol_path)
+
+ # Build the path arrays
+ name_path = []
+ cnid_path = []
+ head, tail = os.path.split(rel_path)
+ if not tail:
+ head, tail = os.path.split(head)
+ while head or tail:
+ if head:
+ attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
+ info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
+ cnid_path.insert(0, info[0])
+ head, tail = os.path.split(head)
+ name_path.insert(0, tail)
+ else:
+ head, tail = os.path.split(head)
+ name_path.append(filename)
+ cnid_path.append(cnid)
+
+ url_lengths = [relcount, len(name_path) - relcount]
+
+ fileprops = Data(struct.pack(b"<QQQ", flags, 0x0F, 0))
+ volprops = Data(
+ struct.pack(
+ b"<QQQ",
+ 0x81 | kCFURLVolumeSupportsPersistentIDs,
+ 0x13EF | kCFURLVolumeSupportsPersistentIDs,
+ 0,
+ )
+ )
+
+ toc = {
+ kBookmarkPath: name_path,
+ kBookmarkCNIDPath: cnid_path,
+ kBookmarkFileCreationDate: crtime,
+ kBookmarkFileProperties: fileprops,
+ kBookmarkContainingFolder: len(name_path) - 2,
+ kBookmarkVolumePath: vol_path,
+ kBookmarkVolumeIsRoot: vol_path == "/",
+ kBookmarkVolumeURL: URL("file://" + vol_path),
+ kBookmarkVolumeName: vol_name,
+ kBookmarkVolumeSize: vol_size,
+ kBookmarkVolumeCreationDate: vol_crtime,
+ kBookmarkVolumeUUID: str(vol_uuid).upper(),
+ kBookmarkVolumeProperties: volprops,
+ kBookmarkCreationOptions: 512,
+ kBookmarkWasFileReference: True,
+ kBookmarkUserName: "unknown",
+ kBookmarkUID: 99,
+ }
+
+ if relcount:
+ toc[kBookmarkURLLengths] = url_lengths
+
+ return Bookmark([(1, toc)])
+
+ def __repr__(self):
+ result = ["Bookmark(["]
+ for tid, toc in self.tocs:
+ result.append("(0x%x, {\n" % tid)
+ for k, v in toc.items():
+ if isinstance(k, str):
+ kf = repr(k)
+ else:
+ kf = "0x%04x" % k
+ result.append(f" {kf}: {v!r}\n")
+ result.append("}),\n")
+ result.append("])")
+
+ return "".join(result)
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/osx.py b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/osx.py
index d4af65d75..1b2feabaa 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/osx.py
+++ b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/osx.py
@@ -1,247 +1,367 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from ctypes import *
-import struct
-import os
import datetime
+import os
import uuid
-
-from .utils import *
-
-libc = cdll.LoadLibrary('/usr/lib/libc.dylib')
+from ctypes import (
+ POINTER,
+ Structure,
+ Union,
+ byref,
+ c_byte,
+ c_char,
+ c_char_p,
+ c_int,
+ c_long,
+ c_longlong,
+ c_short,
+ c_uint,
+ c_ulong,
+ c_ulonglong,
+ c_ushort,
+ c_void_p,
+ cdll,
+ create_string_buffer,
+ sizeof,
+)
+
+from .utils import unix_epoch
+
+libc = cdll.LoadLibrary("/usr/lib/libc.dylib")
# Constants
-FSOPT_NOFOLLOW = 0x00000001
-FSOPT_NOINMEMUPDATE = 0x00000002
-FSOPT_REPORT_FULLSIZE = 0x00000004
+FSOPT_NOFOLLOW = 0x00000001
+FSOPT_NOINMEMUPDATE = 0x00000002
+FSOPT_REPORT_FULLSIZE = 0x00000004
FSOPT_PACK_INVAL_ATTRS = 0x00000008
+FSOPT_ATTR_CMN_EXTENDED = 0x00000020
+FSOPT_RETURN_REALDEV = 0x00000200
-VOL_CAPABILITIES_FORMAT = 0
+VOL_CAPABILITIES_FORMAT = 0
VOL_CAPABILITIES_INTERFACES = 1
-VOL_CAP_FMT_PERSISTENTOBJECTIDS = 0x00000001
-VOL_CAP_FMT_SYMBOLICLINKS = 0x00000002
-VOL_CAP_FMT_HARDLINKS = 0x00000004
-VOL_CAP_FMT_JOURNAL = 0x00000008
-VOL_CAP_FMT_JOURNAL_ACTIVE = 0x00000010
-VOL_CAP_FMT_NO_ROOT_TIMES = 0x00000020
-VOL_CAP_FMT_SPARSE_FILES = 0x00000040
-VOL_CAP_FMT_ZERO_RUNS = 0x00000080
-VOL_CAP_FMT_CASE_SENSITIVE = 0x00000100
-VOL_CAP_FMT_CASE_PRESERVING = 0x00000200
-VOL_CAP_FMT_FAST_STATFS = 0x00000400
-VOL_CAP_FMT_2TB_FILESIZE = 0x00000800
-VOL_CAP_FMT_OPENDENYMODES = 0x00001000
-VOL_CAP_FMT_HIDDEN_FILES = 0x00002000
-VOL_CAP_FMT_PATH_FROM_ID = 0x00004000
-VOL_CAP_FMT_NO_VOLUME_SIZES = 0x00008000
-VOL_CAP_FMT_DECMPFS_COMPRESSION = 0x00010000
-VOL_CAP_FMT_64BIT_OBJECT_IDS = 0x00020000
-
-VOL_CAP_INT_SEARCHFS = 0x00000001
-VOL_CAP_INT_ATTRLIST = 0x00000002
-VOL_CAP_INT_NFSEXPORT = 0x00000004
-VOL_CAP_INT_READDIRATTR = 0x00000008
-VOL_CAP_INT_EXCHANGEDATA = 0x00000010
-VOL_CAP_INT_COPYFILE = 0x00000020
-VOL_CAP_INT_ALLOCATE = 0x00000040
-VOL_CAP_INT_VOL_RENAME = 0x00000080
-VOL_CAP_INT_ADVLOCK = 0x00000100
-VOL_CAP_INT_FLOCK = 0x00000200
-VOL_CAP_INT_EXTENDED_SECURITY = 0x00000400
-VOL_CAP_INT_USERACCESS = 0x00000800
-VOL_CAP_INT_MANLOCK = 0x00001000
-VOL_CAP_INT_NAMEDSTREAMS = 0x00002000
-VOL_CAP_INT_EXTENDED_ATTR = 0x00004000
-
-ATTR_CMN_NAME = 0x00000001
-ATTR_CMN_DEVID = 0x00000002
-ATTR_CMN_FSID = 0x00000004
-ATTR_CMN_OBJTYPE = 0x00000008
-ATTR_CMN_OBJTAG = 0x00000010
-ATTR_CMN_OBJID = 0x00000020
-ATTR_CMN_OBJPERMANENTID = 0x00000040
-ATTR_CMN_PAROBJID = 0x00000080
-ATTR_CMN_SCRIPT = 0x00000100
-ATTR_CMN_CRTIME = 0x00000200
-ATTR_CMN_MODTIME = 0x00000400
-ATTR_CMN_CHGTIME = 0x00000800
-ATTR_CMN_ACCTIME = 0x00001000
-ATTR_CMN_BKUPTIME = 0x00002000
-ATTR_CMN_FNDRINFO = 0x00004000
-ATTR_CMN_OWNERID = 0x00008000
-ATTR_CMN_GRPID = 0x00010000
-ATTR_CMN_ACCESSMASK = 0x00020000
-ATTR_CMN_FLAGS = 0x00040000
-ATTR_CMN_USERACCESS = 0x00200000
-ATTR_CMN_EXTENDED_SECURITY = 0x00400000
-ATTR_CMN_UUID = 0x00800000
-ATTR_CMN_GRPUUID = 0x01000000
-ATTR_CMN_FILEID = 0x02000000
-ATTR_CMN_PARENTID = 0x04000000
-ATTR_CMN_FULLPATH = 0x08000000
-ATTR_CMN_ADDEDTIME = 0x10000000
-ATTR_CMN_RETURNED_ATTRS = 0x80000000
-ATTR_CMN_ALL_ATTRS = 0x9fe7ffff
-
-ATTR_VOL_FSTYPE = 0x00000001
-ATTR_VOL_SIGNATURE = 0x00000002
-ATTR_VOL_SIZE = 0x00000004
-ATTR_VOL_SPACEFREE = 0x00000008
-ATTR_VOL_SPACEAVAIL = 0x00000010
-ATTR_VOL_MINALLOCATION = 0x00000020
-ATTR_VOL_ALLOCATIONCLUMP = 0x00000040
-ATTR_VOL_IOBLOCKSIZE = 0x00000080
-ATTR_VOL_OBJCOUNT = 0x00000100
-ATTR_VOL_FILECOUNT = 0x00000200
-ATTR_VOL_DIRCOUNT = 0x00000400
-ATTR_VOL_MAXOBJCOUNT = 0x00000800
-ATTR_VOL_MOUNTPOINT = 0x00001000
-ATTR_VOL_NAME = 0x00002000
-ATTR_VOL_MOUNTFLAGS = 0x00004000
-ATTR_VOL_MOUNTEDDEVICE = 0x00008000
-ATTR_VOL_ENCODINGSUSED = 0x00010000
-ATTR_VOL_CAPABILITIES = 0x00020000
-ATTR_VOL_UUID = 0x00040000
-ATTR_VOL_ATTRIBUTES = 0x40000000
-ATTR_VOL_INFO = 0x80000000
-ATTR_VOL_ALL_ATTRS = 0xc007ffff
-
-ATTR_DIR_LINKCOUNT = 0x00000001
-ATTR_DIR_ENTRYCOUNT = 0x00000002
-ATTR_DIR_MOUNTSTATUS = 0x00000004
-DIR_MNTSTATUS_MNTPOINT = 0x00000001
-DIR_MNTSTATUS_TRIGGER = 0x00000002
-ATTR_DIR_ALL_ATTRS = 0x00000007
-
-ATTR_FILE_LINKCOUNT = 0x00000001
-ATTR_FILE_TOTALSIZE = 0x00000002
-ATTR_FILE_ALLOCSIZE = 0x00000004
-ATTR_FILE_IOBLOCKSIZE = 0x00000008
-ATTR_FILE_DEVTYPE = 0x00000020
-ATTR_FILE_DATALENGTH = 0x00000200
-ATTR_FILE_DATAALLOCSIZE = 0x00000400
-ATTR_FILE_RSRCLENGTH = 0x00001000
-ATTR_FILE_RSRCALLOCSIZE = 0x00002000
-
-ATTR_FILE_ALL_ATTRS = 0x0000362f
-
-ATTR_FORK_TOTALSIZE = 0x00000001
-ATTR_FORK_ALLOCSIZE = 0x00000002
-ATTR_FORK_ALL_ATTRS = 0x00000003
+VOL_CAP_FMT_PERSISTENTOBJECTIDS = 0x00000001
+VOL_CAP_FMT_SYMBOLICLINKS = 0x00000002
+VOL_CAP_FMT_HARDLINKS = 0x00000004
+VOL_CAP_FMT_JOURNAL = 0x00000008
+VOL_CAP_FMT_JOURNAL_ACTIVE = 0x00000010
+VOL_CAP_FMT_NO_ROOT_TIMES = 0x00000020
+VOL_CAP_FMT_SPARSE_FILES = 0x00000040
+VOL_CAP_FMT_ZERO_RUNS = 0x00000080
+VOL_CAP_FMT_CASE_SENSITIVE = 0x00000100
+VOL_CAP_FMT_CASE_PRESERVING = 0x00000200
+VOL_CAP_FMT_FAST_STATFS = 0x00000400
+VOL_CAP_FMT_2TB_FILESIZE = 0x00000800
+VOL_CAP_FMT_OPENDENYMODES = 0x00001000
+VOL_CAP_FMT_HIDDEN_FILES = 0x00002000
+VOL_CAP_FMT_PATH_FROM_ID = 0x00004000
+VOL_CAP_FMT_NO_VOLUME_SIZES = 0x00008000
+VOL_CAP_FMT_DECMPFS_COMPRESSION = 0x00010000
+VOL_CAP_FMT_64BIT_OBJECT_IDS = 0x00020000
+
+VOL_CAP_INT_SEARCHFS = 0x00000001
+VOL_CAP_INT_ATTRLIST = 0x00000002
+VOL_CAP_INT_NFSEXPORT = 0x00000004
+VOL_CAP_INT_READDIRATTR = 0x00000008
+VOL_CAP_INT_EXCHANGEDATA = 0x00000010
+VOL_CAP_INT_COPYFILE = 0x00000020
+VOL_CAP_INT_ALLOCATE = 0x00000040
+VOL_CAP_INT_VOL_RENAME = 0x00000080
+VOL_CAP_INT_ADVLOCK = 0x00000100
+VOL_CAP_INT_FLOCK = 0x00000200
+VOL_CAP_INT_EXTENDED_SECURITY = 0x00000400
+VOL_CAP_INT_USERACCESS = 0x00000800
+VOL_CAP_INT_MANLOCK = 0x00001000
+VOL_CAP_INT_NAMEDSTREAMS = 0x00002000
+VOL_CAP_INT_EXTENDED_ATTR = 0x00004000
+VOL_CAP_INT_CLONE = 0x00010000
+VOL_CAP_INT_SNAPSHOT = 0x00020000
+VOL_CAP_INT_RENAME_SWAP = 0x00040000
+VOL_CAP_INT_RENAME_EXCL = 0x00080000
+VOL_CAP_INT_RENAME_OPENFAIL = 0x00100000
+
+ATTR_CMN_NAME = 0x00000001
+ATTR_CMN_DEVID = 0x00000002
+ATTR_CMN_FSID = 0x00000004
+ATTR_CMN_OBJTYPE = 0x00000008
+ATTR_CMN_OBJTAG = 0x00000010
+ATTR_CMN_OBJID = 0x00000020
+ATTR_CMN_OBJPERMANENTID = 0x00000040
+ATTR_CMN_PAROBJID = 0x00000080
+ATTR_CMN_SCRIPT = 0x00000100
+ATTR_CMN_CRTIME = 0x00000200
+ATTR_CMN_MODTIME = 0x00000400
+ATTR_CMN_CHGTIME = 0x00000800
+ATTR_CMN_ACCTIME = 0x00001000
+ATTR_CMN_BKUPTIME = 0x00002000
+ATTR_CMN_FNDRINFO = 0x00004000
+ATTR_CMN_OWNERID = 0x00008000
+ATTR_CMN_GRPID = 0x00010000
+ATTR_CMN_ACCESSMASK = 0x00020000
+ATTR_CMN_FLAGS = 0x00040000
+ATTR_CMN_GEN_COUNT = 0x00080000
+ATTR_CMN_DOCUMENT_ID = 0x00100000
+ATTR_CMN_USERACCESS = 0x00200000
+ATTR_CMN_EXTENDED_SECURITY = 0x00400000
+ATTR_CMN_UUID = 0x00800000
+ATTR_CMN_GRPUUID = 0x01000000
+ATTR_CMN_FILEID = 0x02000000
+ATTR_CMN_PARENTID = 0x04000000
+ATTR_CMN_FULLPATH = 0x08000000
+ATTR_CMN_ADDEDTIME = 0x10000000
+ATTR_CMN_ERROR = 0x20000000
+ATTR_CMN_DATA_PROTECT_FLAGS = 0x40000000
+ATTR_CMN_RETURNED_ATTRS = 0x80000000
+ATTR_CMN_ALL_ATTRS = 0xFFFFFFFF
+
+ATTR_CMN_VALIDMASK = 0xFFFFFFFF
+ATTR_CMN_SETMASK = 0x51C7FF00
+ATTR_CMN_VOLSETMASK = 0x00006700
+
+ATTR_VOL_FSTYPE = 0x00000001
+ATTR_VOL_SIGNATURE = 0x00000002
+ATTR_VOL_SIZE = 0x00000004
+ATTR_VOL_SPACEFREE = 0x00000008
+ATTR_VOL_SPACEAVAIL = 0x00000010
+ATTR_VOL_MINALLOCATION = 0x00000020
+ATTR_VOL_ALLOCATIONCLUMP = 0x00000040
+ATTR_VOL_IOBLOCKSIZE = 0x00000080
+ATTR_VOL_OBJCOUNT = 0x00000100
+ATTR_VOL_FILECOUNT = 0x00000200
+ATTR_VOL_DIRCOUNT = 0x00000400
+ATTR_VOL_MAXOBJCOUNT = 0x00000800
+ATTR_VOL_MOUNTPOINT = 0x00001000
+ATTR_VOL_NAME = 0x00002000
+ATTR_VOL_MOUNTFLAGS = 0x00004000
+ATTR_VOL_MOUNTEDDEVICE = 0x00008000
+ATTR_VOL_ENCODINGSUSED = 0x00010000
+ATTR_VOL_CAPABILITIES = 0x00020000
+ATTR_VOL_UUID = 0x00040000
+ATTR_VOL_QUOTA_SIZE = 0x10000000
+ATTR_VOL_RESERVED_SIZE = 0x20000000
+ATTR_VOL_ATTRIBUTES = 0x40000000
+ATTR_VOL_INFO = 0x80000000
+ATTR_VOL_ALL_ATTRS = 0xF007FFFF
+
+ATTR_DIR_LINKCOUNT = 0x00000001
+ATTR_DIR_ENTRYCOUNT = 0x00000002
+ATTR_DIR_MOUNTSTATUS = 0x00000004
+DIR_MNTSTATUS_MNTPOINT = 0x00000001
+DIR_MNTSTATUS_TRIGGER = 0x00000002
+ATTR_DIR_ALLOCSIZE = 0x00000008
+ATTR_DIR_IOBLOCKSIZE = 0x00000010
+ATTR_DIR_DATALENGTH = 0x00000020
+ATTR_DIR_ALL_ATTRS = 0x0000003F
+
+ATTR_DIR_VALIDMASK = 0x0000003F
+ATTR_DIR_SETMASK = 0x00000000
+
+ATTR_FILE_LINKCOUNT = 0x00000001
+ATTR_FILE_TOTALSIZE = 0x00000002
+ATTR_FILE_ALLOCSIZE = 0x00000004
+ATTR_FILE_IOBLOCKSIZE = 0x00000008
+ATTR_FILE_DEVTYPE = 0x00000020
+ATTR_FILE_FORKCOUNT = 0x00000080
+ATTR_FILE_FORKLIST = 0x00000100
+ATTR_FILE_DATALENGTH = 0x00000200
+ATTR_FILE_DATAALLOCSIZE = 0x00000400
+ATTR_FILE_RSRCLENGTH = 0x00001000
+ATTR_FILE_RSRCALLOCSIZE = 0x00002000
+ATTR_FILE_ALL_ATTRS = 0x000037FF
+
+ATTR_FILE_VALIDMASK = 0x000037FF
+ATTR_FILE_SETMASK = 0x00000020
+
+# These are deprecated
+ATTR_FORK_TOTALSIZE = 0x00000001
+ATTR_FORK_ALLOCSIZE = 0x00000002
+ATTR_FORK_ALL_ATTRS = 0x00000003
+
+# These go in the fork attribute field
+ATTR_CMNEXT_RELPATH = 0x00000004
+ATTR_CMNEXT_PRIVATESIZE = 0x00000008
+ATTR_CMNEXT_LINKID = 0x0000010
+ATTR_CMNEXT_NOFIRMLINKPATH = 0x00000020
+ATTR_CMNEXT_REALDEVID = 0x00000040
+ATTR_CMNEXT_REALFSID = 0x00000080
+ATTR_CMNEXT_CLONEID = 0x00000100
+ATTR_CMNEXT_EXT_FLAGS = 0x00000200
+ATTR_CMNEXT_RECURSIVE_GENCOUNT = 0x00000400
+ATTR_CMNEXT_ALL_ATTRS = 0x000007FC
+
+ATTR_CMNEXT_VALIDMASK = 0x000007FC
+ATTR_CMNEXT_SETMASK = 0x00000000
+
+ATTR_FORK_VALIDMASK = 0x00000003
+ATTR_FORK_SETMASK = 0x00000000
# These can't be used
-ATTR_FILE_FORKCOUNT = 0x00000080
-ATTR_FILE_FORKLIST = 0x00000100
-ATTR_CMN_NAMEDATTRCOUNT = 0x00080000
-ATTR_CMN_NAMEDATTRLIST = 0x00100000
-ATTR_FILE_DATAEXTENTS = 0x00000800
-ATTR_FILE_RSRCEXTENTS = 0x00004000
-ATTR_FILE_CLUMPSIZE = 0x00000010
-ATTR_FILE_FILETYPE = 0x00000040
+ATTR_CMN_NAMEDATTRCOUNT = 0x00080000
+ATTR_CMN_NAMEDATTRLIST = 0x00100000
+ATTR_FILE_CLUMPSIZE = 0x00000010
+ATTR_FILE_FILETYPE = 0x00000040
+ATTR_FILE_DATAEXTENTS = 0x00000800
+ATTR_FILE_RSRCEXTENTS = 0x00004000
+
class attrlist(Structure):
- _fields_ = [('bitmapcount', c_ushort),
- ('reserved', c_ushort),
- ('commonattr', c_uint),
- ('volattr', c_uint),
- ('dirattr', c_uint),
- ('fileattr', c_uint),
- ('forkattr', c_uint)]
+ _fields_ = [
+ ("bitmapcount", c_ushort),
+ ("reserved", c_ushort),
+ ("commonattr", c_uint),
+ ("volattr", c_uint),
+ ("dirattr", c_uint),
+ ("fileattr", c_uint),
+ ("forkattr", c_uint),
+ ]
+
class attribute_set_t(Structure):
- _fields_ = [('commonattr', c_uint),
- ('volattr', c_uint),
- ('dirattr', c_uint),
- ('fileattr', c_uint),
- ('forkattr', c_uint)]
+ _fields_ = [
+ ("commonattr", c_uint),
+ ("volattr", c_uint),
+ ("dirattr", c_uint),
+ ("fileattr", c_uint),
+ ("forkattr", c_uint),
+ ]
+
class fsobj_id_t(Structure):
- _fields_ = [('fid_objno', c_uint),
- ('fid_generation', c_uint)]
+ _fields_ = [
+ ("fid_objno", c_uint),
+ ("fid_generation", c_uint),
+ ]
+
class timespec(Structure):
- _fields_ = [('tv_sec', c_long),
- ('tv_nsec', c_long)]
+ _fields_ = [
+ ("tv_sec", c_long),
+ ("tv_nsec", c_long),
+ ]
+
class attrreference_t(Structure):
- _fields_ = [('attr_dataoffset', c_int),
- ('attr_length', c_uint)]
+ _fields_ = [
+ ("attr_dataoffset", c_int),
+ ("attr_length", c_uint),
+ ]
+
class fsid_t(Structure):
- _fields_ = [('val', c_uint * 2)]
+ _fields_ = [
+ ("val", c_uint * 2),
+ ]
+
class guid_t(Structure):
- _fields_ = [('g_guid', c_byte*16)]
+ _fields_ = [
+ ("g_guid", c_byte * 16),
+ ]
+
class kauth_ace(Structure):
- _fields_ = [('ace_applicable', guid_t),
- ('ace_flags', c_uint)]
+ _fields_ = [
+ ("ace_applicable", guid_t),
+ ("ace_flags", c_uint),
+ ]
+
class kauth_acl(Structure):
- _fields_ = [('acl_entrycount', c_uint),
- ('acl_flags', c_uint),
- ('acl_ace', kauth_ace * 128)]
+ _fields_ = [
+ ("acl_entrycount", c_uint),
+ ("acl_flags", c_uint),
+ ("acl_ace", kauth_ace * 128),
+ ]
+
class kauth_filesec(Structure):
- _fields_ = [('fsec_magic', c_uint),
- ('fsec_owner', guid_t),
- ('fsec_group', guid_t),
- ('fsec_acl', kauth_acl)]
+ _fields_ = [
+ ("fsec_magic", c_uint),
+ ("fsec_owner", guid_t),
+ ("fsec_group", guid_t),
+ ("fsec_acl", kauth_acl),
+ ]
+
class diskextent(Structure):
- _fields_ = [('startblock', c_uint),
- ('blockcount', c_uint)]
+ _fields_ = [
+ ("startblock", c_uint),
+ ("blockcount", c_uint),
+ ]
+
OSType = c_uint
UInt16 = c_ushort
SInt16 = c_short
SInt32 = c_int
+
class Point(Structure):
- _fields_ = [('x', SInt16),
- ('y', SInt16)]
+ _fields_ = [
+ ("x", SInt16),
+ ("y", SInt16),
+ ]
+
+
class Rect(Structure):
- _fields_ = [('x', SInt16),
- ('y', SInt16),
- ('w', SInt16),
- ('h', SInt16)]
+ _fields_ = [
+ ("x", SInt16),
+ ("y", SInt16),
+ ("w", SInt16),
+ ("h", SInt16),
+ ]
+
+
class FileInfo(Structure):
- _fields_ = [('fileType', OSType),
- ('fileCreator', OSType),
- ('finderFlags', UInt16),
- ('location', Point),
- ('reservedField', UInt16),
- ('reserved1', SInt16 * 4),
- ('extendedFinderFlags', UInt16),
- ('reserved2', SInt16),
- ('putAwayFolderID', SInt32)]
+ _fields_ = [
+ ("fileType", OSType),
+ ("fileCreator", OSType),
+ ("finderFlags", UInt16),
+ ("location", Point),
+ ("reservedField", UInt16),
+ ("reserved1", SInt16 * 4),
+ ("extendedFinderFlags", UInt16),
+ ("reserved2", SInt16),
+ ("putAwayFolderID", SInt32),
+ ]
+
+
class FolderInfo(Structure):
- _fields_ = [('windowBounds', Rect),
- ('finderFlags', UInt16),
- ('location', Point),
- ('reservedField', UInt16),
- ('scrollPosition', Point),
- ('reserved1', SInt32),
- ('extendedFinderFlags', UInt16),
- ('reserved2', SInt16),
- ('putAwayFolderID', SInt32)]
+ _fields_ = [
+ ("windowBounds", Rect),
+ ("finderFlags", UInt16),
+ ("location", Point),
+ ("reservedField", UInt16),
+ ("scrollPosition", Point),
+ ("reserved1", SInt32),
+ ("extendedFinderFlags", UInt16),
+ ("reserved2", SInt16),
+ ("putAwayFolderID", SInt32),
+ ]
+
+
class FinderInfo(Union):
- _fields_ = [('fileInfo', FileInfo),
- ('folderInfo', FolderInfo)]
+ _fields_ = [
+ ("fileInfo", FileInfo),
+ ("folderInfo", FolderInfo),
+ ]
+
extentrecord = diskextent * 8
-
vol_capabilities_set_t = c_uint * 4
+
class vol_capabilities_attr_t(Structure):
- _fields_ = [('capabilities', vol_capabilities_set_t),
- ('valid', vol_capabilities_set_t)]
+ _fields_ = [
+ ("capabilities", vol_capabilities_set_t),
+ ("valid", vol_capabilities_set_t),
+ ]
+
class vol_attributes_attr_t(Structure):
- _fields_ = [('validattr', attribute_set_t),
- ('nativeattr', attribute_set_t)]
+ _fields_ = [
+ ("validattr", attribute_set_t),
+ ("nativeattr", attribute_set_t),
+ ]
+
dev_t = c_uint
@@ -298,28 +418,34 @@ int64_t = c_longlong
uint64_t = c_ulonglong
off_t = c_long
size_t = c_ulong
-uuid_t = c_byte*16
+uuid_t = c_byte * 16
NAME_MAX = 255
PATH_MAX = 1024
+FSTYPE_MAX = 16
+
class struct_statfs(Structure):
- _fields_ = [('f_bsize', uint32_t),
- ('f_iosize', int32_t),
- ('f_blocks', uint64_t),
- ('f_bfree', uint64_t),
- ('f_bavail', uint64_t),
- ('f_files', uint64_t),
- ('f_ffree', uint64_t),
- ('f_fsid', fsid_t),
- ('f_owner', uid_t),
- ('f_type', uint32_t),
- ('f_flags', uint32_t),
- ('f_fssubtype', uint32_t),
- ('f_fstypename', c_char * 16),
- ('f_mntonname', c_char * PATH_MAX),
- ('f_mntfromname', c_char * PATH_MAX),
- ('f_reserved', uint32_t * 8)]
+ _fields_ = [
+ ("f_bsize", uint32_t),
+ ("f_iosize", int32_t),
+ ("f_blocks", uint64_t),
+ ("f_bfree", uint64_t),
+ ("f_bavail", uint64_t),
+ ("f_files", uint64_t),
+ ("f_ffree", uint64_t),
+ ("f_fsid", fsid_t),
+ ("f_owner", uid_t),
+ ("f_type", uint32_t),
+ ("f_flags", uint32_t),
+ ("f_fssubtype", uint32_t),
+ ("f_fstypename", c_char * FSTYPE_MAX),
+ ("f_mntonname", c_char * PATH_MAX),
+ ("f_mntfromname", c_char * PATH_MAX),
+ ("f_flags_ext", uint32_t),
+ ("f_reserved", uint32_t * 7),
+ ]
+
# Calculate the maximum number of bytes required for the attribute buffer
_attr_info = (
@@ -330,6 +456,7 @@ _attr_info = (
(0, ATTR_CMN_FSID, sizeof(fsid_t)),
(0, ATTR_CMN_OBJTYPE, sizeof(fsobj_type_t)),
(0, ATTR_CMN_OBJTAG, sizeof(fsobj_tag_t)),
+ (0, ATTR_CMN_OBJID, sizeof(fsobj_id_t)),
(0, ATTR_CMN_OBJPERMANENTID, sizeof(fsobj_id_t)),
(0, ATTR_CMN_PAROBJID, sizeof(fsobj_id_t)),
(0, ATTR_CMN_SCRIPT, sizeof(text_encoding_t)),
@@ -345,6 +472,8 @@ _attr_info = (
(0, ATTR_CMN_NAMEDATTRCOUNT, None),
(0, ATTR_CMN_NAMEDATTRLIST, None),
(0, ATTR_CMN_FLAGS, sizeof(uint32_t)),
+ (0, ATTR_CMN_GEN_COUNT, sizeof(uint32_t)),
+ (0, ATTR_CMN_DOCUMENT_ID, sizeof(uint32_t)),
(0, ATTR_CMN_USERACCESS, sizeof(uint32_t)),
(0, ATTR_CMN_EXTENDED_SECURITY, sizeof(attrreference_t) + sizeof(kauth_filesec)),
(0, ATTR_CMN_UUID, sizeof(guid_t)),
@@ -353,7 +482,7 @@ _attr_info = (
(0, ATTR_CMN_PARENTID, sizeof(uint64_t)),
(0, ATTR_CMN_FULLPATH, sizeof(attrreference_t) + PATH_MAX),
(0, ATTR_CMN_ADDEDTIME, sizeof(timespec)),
-
+ (0, ATTR_CMN_DATA_PROTECT_FLAGS, sizeof(uint32_t)),
# Volume attributes
(1, ATTR_VOL_FSTYPE, sizeof(uint32_t)),
(1, ATTR_VOL_SIGNATURE, sizeof(uint32_t)),
@@ -374,13 +503,16 @@ _attr_info = (
(1, ATTR_VOL_ENCODINGSUSED, sizeof(c_ulonglong)),
(1, ATTR_VOL_CAPABILITIES, sizeof(vol_capabilities_attr_t)),
(1, ATTR_VOL_UUID, sizeof(uuid_t)),
+ (1, ATTR_VOL_QUOTA_SIZE, sizeof(off_t)),
+ (1, ATTR_VOL_RESERVED_SIZE, sizeof(off_t)),
(1, ATTR_VOL_ATTRIBUTES, sizeof(vol_attributes_attr_t)),
-
# Directory attributes
(2, ATTR_DIR_LINKCOUNT, sizeof(uint32_t)),
(2, ATTR_DIR_ENTRYCOUNT, sizeof(uint32_t)),
(2, ATTR_DIR_MOUNTSTATUS, sizeof(uint32_t)),
-
+ (2, ATTR_DIR_ALLOCSIZE, sizeof(off_t)),
+ (2, ATTR_DIR_IOBLOCKSIZE, sizeof(uint32_t)),
+ (2, ATTR_DIR_DATALENGTH, sizeof(off_t)),
# File attributes
(3, ATTR_FILE_LINKCOUNT, sizeof(uint32_t)),
(3, ATTR_FILE_TOTALSIZE, sizeof(off_t)),
@@ -397,22 +529,33 @@ _attr_info = (
(3, ATTR_FILE_RSRCLENGTH, sizeof(off_t)),
(3, ATTR_FILE_RSRCALLOCSIZE, sizeof(off_t)),
(3, ATTR_FILE_RSRCEXTENTS, sizeof(extentrecord)),
-
# Fork attributes
(4, ATTR_FORK_TOTALSIZE, sizeof(off_t)),
- (4, ATTR_FORK_ALLOCSIZE, sizeof(off_t))
- )
-
+ (4, ATTR_FORK_ALLOCSIZE, sizeof(off_t)),
+ # Extended common attributes
+ (4, ATTR_CMNEXT_RELPATH, sizeof(attrreference_t) + PATH_MAX),
+ (4, ATTR_CMNEXT_PRIVATESIZE, sizeof(off_t)),
+ (4, ATTR_CMNEXT_LINKID, sizeof(uint64_t)),
+ (4, ATTR_CMNEXT_NOFIRMLINKPATH, sizeof(attrreference_t) + PATH_MAX),
+ (4, ATTR_CMNEXT_REALDEVID, sizeof(dev_t)),
+ (4, ATTR_CMNEXT_REALFSID, sizeof(fsid_t)),
+ (4, ATTR_CMNEXT_CLONEID, sizeof(uint64_t)),
+ (4, ATTR_CMNEXT_EXT_FLAGS, sizeof(uint64_t)),
+)
+
+
def _attrbuf_size(attrs):
size = 4
for entry in _attr_info:
if attrs[entry[0]] & entry[1]:
if entry[2] is None:
- raise ValueError('Unsupported attribute (%u, %x)'
- % (entry[0], entry[1]))
+ raise ValueError(
+ "Unsupported attribute (%u, %x)" % (entry[0], entry[1])
+ )
size += entry[2]
return size
+
_getattrlist = libc.getattrlist
_getattrlist.argtypes = [c_char_p, POINTER(attrlist), c_void_p, c_ulong, c_ulong]
_getattrlist.restype = c_int
@@ -421,23 +564,40 @@ _fgetattrlist = libc.fgetattrlist
_fgetattrlist.argtypes = [c_int, POINTER(attrlist), c_void_p, c_ulong, c_ulong]
_fgetattrlist.restype = c_int
-_statfs = libc['statfs$INODE64']
-_statfs.argtypes = [c_char_p, POINTER(struct_statfs)]
+try:
+ _statfs = libc["statfs$INODE64"]
+except (KeyError, AttributeError):
+ _statfs = libc["statfs"]
+
+_statfs.argtypes = [
+ c_char_p,
+ POINTER(struct_statfs),
+]
_statfs.restype = c_int
-_fstatfs = libc['fstatfs$INODE64']
-_fstatfs.argtypes = [c_int, POINTER(struct_statfs)]
+try:
+ _fstatfs = libc["fstatfs$INODE64"]
+except (KeyError, AttributeError):
+ _fstatfs = libc["fstatfs"]
+
+_fstatfs.argtypes = [
+ c_int,
+ POINTER(struct_statfs),
+]
_fstatfs.restype = c_int
+
def _datetime_from_timespec(ts):
td = datetime.timedelta(seconds=ts.tv_sec + 1.0e-9 * ts.tv_nsec)
return unix_epoch + td
+
def _decode_utf8_nul(sz):
- nul = sz.find(b'\0')
+ nul = sz.find(b"\0")
if nul > -1:
sz = sz[:nul]
- return sz.decode('utf-8')
+ return sz.decode("utf-8")
+
def _decode_attrlist_result(buf, attrs, options):
result = []
@@ -452,14 +612,14 @@ def _decode_attrlist_result(buf, attrs, options):
if attrs[0] & ATTR_CMN_RETURNED_ATTRS:
a = attribute_set_t.from_buffer(buf, offset)
result.append(a)
- offset += sizeof (attribute_set_t)
+ offset += sizeof(attribute_set_t)
if not (options & FSOPT_PACK_INVAL_ATTRS):
attrs = [a.commonattr, a.volattr, a.dirattr, a.fileattr, a.forkattr]
if attrs[0] & ATTR_CMN_NAME:
a = attrreference_t.from_buffer(buf, offset)
ofs = offset + a.attr_dataoffset
- name = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
- offset += sizeof (attrreference_t)
+ name = _decode_utf8_nul(buf[ofs : ofs + a.attr_length])
+ offset += sizeof(attrreference_t)
result.append(name)
if attrs[0] & ATTR_CMN_DEVID:
a = dev_t.from_buffer(buf, offset)
@@ -533,6 +693,14 @@ def _decode_attrlist_result(buf, attrs, options):
a = uint32_t.from_buffer(buf, offset)
offset += sizeof(uint32_t)
result.append(a.value)
+ if attrs[0] & ATTR_CMN_GEN_COUNT:
+ a = uint32_t.from_buffer(buf, offset)
+ offset += sizeof(uint32_t)
+ result.append(a.value)
+ if attrs[0] & ATTR_CMN_DOCUMENT_ID:
+ a = uint32_t.from_buffer(buf, offset)
+ offset += sizeof(uint32_t)
+ result.append(a.value)
if attrs[0] & ATTR_CMN_USERACCESS:
a = uint32_t.from_buffer(buf, offset)
offset += sizeof(uint32_t)
@@ -542,22 +710,29 @@ def _decode_attrlist_result(buf, attrs, options):
ofs = offset + a.attr_dataoffset
offset += sizeof(attrreference_t)
ec = uint32_t.from_buffer(buf, ofs + 36).value
+
class kauth_acl(Structure):
- _fields_ = [('acl_entrycount', c_uint),
- ('acl_flags', c_uint),
- ('acl_ace', kauth_ace * ec)]
+ _fields_ = [
+ ("acl_entrycount", c_uint),
+ ("acl_flags", c_uint),
+ ("acl_ace", kauth_ace * ec),
+ ]
+
class kauth_filesec(Structure):
- _fields_ = [('fsec_magic', c_uint),
- ('fsec_owner', guid_t),
- ('fsec_group', guid_t),
- ('fsec_acl', kauth_acl)]
+ _fields_ = [
+ ("fsec_magic", c_uint),
+ ("fsec_owner", guid_t),
+ ("fsec_group", guid_t),
+ ("fsec_acl", kauth_acl),
+ ]
+
a = kauth_filesec.from_buffer(buf, ofs)
result.append(a)
if attrs[0] & ATTR_CMN_UUID:
- result.append(uuid.UUID(bytes=buf[offset:offset+16]))
+ result.append(uuid.UUID(bytes=buf[offset : offset + 16]))
offset += sizeof(guid_t)
if attrs[0] & ATTR_CMN_GRPUUID:
- result.append(uuid.UUID(bytes=buf[offset:offset+16]))
+ result.append(uuid.UUID(bytes=buf[offset : offset + 16]))
offset += sizeof(guid_t)
if attrs[0] & ATTR_CMN_FILEID:
a = uint64_t.from_buffer(buf, offset)
@@ -570,13 +745,17 @@ def _decode_attrlist_result(buf, attrs, options):
if attrs[0] & ATTR_CMN_FULLPATH:
a = attrreference_t.from_buffer(buf, offset)
ofs = offset + a.attr_dataoffset
- path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
- offset += sizeof (attrreference_t)
+ path = _decode_utf8_nul(buf[ofs : ofs + a.attr_length])
+ offset += sizeof(attrreference_t)
result.append(path)
if attrs[0] & ATTR_CMN_ADDEDTIME:
a = timespec.from_buffer(buf, offset)
offset += sizeof(timespec)
result.append(_datetime_from_timespec(a))
+ if attrs[0] & ATTR_CMN_DATA_PROTECT_FLAGS:
+ a = uint32_t.from_buffer(buf, offset)
+ offset += sizeof(uint32_t)
+ result.append(a.value)
# Volume attributes
if attrs[1] & ATTR_VOL_FSTYPE:
@@ -630,14 +809,14 @@ def _decode_attrlist_result(buf, attrs, options):
if attrs[1] & ATTR_VOL_MOUNTPOINT:
a = attrreference_t.from_buffer(buf, offset)
ofs = offset + a.attr_dataoffset
- path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
- offset += sizeof (attrreference_t)
+ path = _decode_utf8_nul(buf[ofs : ofs + a.attr_length])
+ offset += sizeof(attrreference_t)
result.append(path)
if attrs[1] & ATTR_VOL_NAME:
a = attrreference_t.from_buffer(buf, offset)
ofs = offset + a.attr_dataoffset
- name = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
- offset += sizeof (attrreference_t)
+ name = _decode_utf8_nul(buf[ofs : ofs + a.attr_length])
+ offset += sizeof(attrreference_t)
result.append(name)
if attrs[1] & ATTR_VOL_MOUNTFLAGS:
a = uint32_t.from_buffer(buf, offset)
@@ -646,8 +825,8 @@ def _decode_attrlist_result(buf, attrs, options):
if attrs[1] & ATTR_VOL_MOUNTEDDEVICE:
a = attrreference_t.from_buffer(buf, offset)
ofs = offset + a.attr_dataoffset
- path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length])
- offset += sizeof (attrreference_t)
+ path = _decode_utf8_nul(buf[ofs : ofs + a.attr_length])
+ offset += sizeof(attrreference_t)
result.append(path)
if attrs[1] & ATTR_VOL_ENCODINGSUSED:
a = c_ulonglong.from_buffer(buf, offset)
@@ -658,8 +837,16 @@ def _decode_attrlist_result(buf, attrs, options):
offset += sizeof(vol_capabilities_attr_t)
result.append(a)
if attrs[1] & ATTR_VOL_UUID:
- result.append(uuid.UUID(bytes=buf[offset:offset+16]))
+ result.append(uuid.UUID(bytes=buf[offset : offset + 16]))
offset += sizeof(uuid_t)
+ if attrs[1] & ATTR_VOL_QUOTA_SIZE:
+ a = off_t.from_buffer(buf, offset)
+ offset += sizeof(off_t)
+ result.append(a.value)
+ if attrs[1] & ATTR_VOL_RESERVED_SIZE:
+ a = off_t.from_buffer(buf, offset)
+ offset += sizeof(off_t)
+ result.append(a.value)
if attrs[1] & ATTR_VOL_ATTRIBUTES:
a = vol_attributes_attr_t.from_buffer(buf, offset)
offset += sizeof(vol_attributes_attr_t)
@@ -678,6 +865,18 @@ def _decode_attrlist_result(buf, attrs, options):
a = uint32_t.from_buffer(buf, offset)
offset += sizeof(uint32_t)
result.append(a.value)
+ if attrs[2] & ATTR_DIR_ALLOCSIZE:
+ a = off_t.from_buffer(buf, offset)
+ offset += sizeof(off_t)
+ result.append(a.value)
+ if attrs[2] & ATTR_DIR_IOBLOCKSIZE:
+ a = uint32_t.from_buffer(buf, offset)
+ offset += sizeof(uint32_t)
+ result.append(a.value)
+ if attrs[2] & ATTR_DIR_DATALENGTH:
+ a = off_t.from_buffer(buf, offset)
+ offset += sizeof(off_t)
+ result.append(a.value)
# File attributes
if attrs[3] & ATTR_FILE_LINKCOUNT:
@@ -746,34 +945,78 @@ def _decode_attrlist_result(buf, attrs, options):
a = off_t.from_buffer(buf, offset)
offset += sizeof(off_t)
result.append(a.value)
-
+
+ # Extended common attributes
+ if attrs[4] & ATTR_CMNEXT_RELPATH:
+ a = attrreference_t.from_buffer(buf, offset)
+ ofs = offset + a.attr_dataoffset
+ path = _decode_utf8_nul(buf[ofs : ofs + a.attr_length])
+ offset += sizeof(attrreference_t)
+ result.append(path)
+ if attrs[4] & ATTR_CMNEXT_PRIVATESIZE:
+ a = off_t.from_buffer(buf, offset)
+ offset += sizeof(off_t)
+ result.append(a.value)
+ if attrs[4] & ATTR_CMNEXT_LINKID:
+ a = uint64_t.from_buffer(buf, offset)
+ offset += sizeof(uint64_t)
+ result.append(a.value)
+ if attrs[4] & ATTR_CMNEXT_NOFIRMLINKPATH:
+ a = attrreference_t.from_buffer(buf, offset)
+ ofs = offset + a.attr_dataoffset
+ path = _decode_utf8_nul(buf[ofs : ofs + a.attr_length])
+ offset += sizeof(attrreference_t)
+ result.append(path)
+ if attrs[4] & ATTR_CMNEXT_REALDEVID:
+ a = dev_t.from_buffer(buf, offset)
+ offset += sizeof(dev_t)
+ result.append(a.value)
+ if attrs[4] & ATTR_CMNEXT_REALFSID:
+ a = fsid_t.from_buffer(buf, offset)
+ offset += sizeof(fsid_t)
+ result.append(a.value)
+ if attrs[4] & ATTR_CMNEXT_CLONEID:
+ a = uint64_t.from_buffer(buf, offset)
+ offset += sizeof(uint64_t)
+ result.append(a.value)
+ if attrs[4] & ATTR_CMNEXT_EXT_FLAGS:
+ a = uint64_t.from_buffer(buf, offset)
+ offset += sizeof(uint64_t)
+ result.append(a.value)
+
return result
+
# Sadly, ctypes.get_errno() seems not to work
__error = libc.__error
__error.restype = POINTER(c_int)
+
def _get_errno():
return __error().contents.value
-
+
+
def getattrlist(path, attrs, options):
if not isinstance(path, bytes):
- path = path.encode('utf-8')
+ path = path.encode("utf-8")
attrs = list(attrs)
if attrs[1]:
attrs[1] |= ATTR_VOL_INFO
- alist = attrlist(bitmapcount=5,
- commonattr=attrs[0],
- volattr=attrs[1],
- dirattr=attrs[2],
- fileattr=attrs[3],
- forkattr=attrs[4])
+ alist = attrlist(
+ bitmapcount=5,
+ commonattr=attrs[0],
+ volattr=attrs[1],
+ dirattr=attrs[2],
+ fileattr=attrs[3],
+ forkattr=attrs[4],
+ )
bufsize = _attrbuf_size(attrs)
buf = create_string_buffer(bufsize)
- ret = _getattrlist(path, byref(alist), buf, bufsize,
- options | FSOPT_REPORT_FULLSIZE)
+ ret = _getattrlist(
+ path, byref(alist), buf, bufsize, options | FSOPT_REPORT_FULLSIZE
+ )
if ret < 0:
err = _get_errno()
@@ -781,24 +1024,26 @@ def getattrlist(path, attrs, options):
return _decode_attrlist_result(buf, attrs, options)
+
def fgetattrlist(fd, attrs, options):
- if hasattr(fd, 'fileno'):
+ if hasattr(fd, "fileno"):
fd = fd.fileno()
attrs = list(attrs)
if attrs[1]:
attrs[1] |= ATTR_VOL_INFO
- alist = attrlist(bitmapcount=5,
- commonattr=attrs[0],
- volattr=attrs[1],
- dirattr=attrs[2],
- fileattr=attrs[3],
- forkattr=attrs[4])
+ alist = attrlist(
+ bitmapcount=5,
+ commonattr=attrs[0],
+ volattr=attrs[1],
+ dirattr=attrs[2],
+ fileattr=attrs[3],
+ forkattr=attrs[4],
+ )
bufsize = _attrbuf_size(attrs)
buf = create_string_buffer(bufsize)
- ret = _fgetattrlist(fd, byref(alist), buf, bufsize,
- options | FSOPT_REPORT_FULLSIZE)
+ ret = _fgetattrlist(fd, byref(alist), buf, bufsize, options | FSOPT_REPORT_FULLSIZE)
if ret < 0:
err = _get_errno()
@@ -806,9 +1051,10 @@ def fgetattrlist(fd, attrs, options):
return _decode_attrlist_result(buf, attrs, options)
+
def statfs(path):
if not isinstance(path, bytes):
- path = path.encode('utf-8')
+ path = path.encode("utf-8")
result = struct_statfs()
ret = _statfs(path, byref(result))
if ret < 0:
@@ -816,8 +1062,9 @@ def statfs(path):
raise OSError(err, os.strerror(err), path)
return result
+
def fstatfs(fd):
- if hasattr(fd, 'fileno'):
+ if hasattr(fd, "fileno"):
fd = fd.fileno()
result = struct_statfs()
ret = _fstatfs(fd, byref(result))
diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/qt_attribution.json b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/qt_attribution.json
index 968b6318c..968b6318c 100644
--- a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/qt_attribution.json
+++ b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/qt_attribution.json
diff --git a/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/utils.py b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/utils.py
new file mode 100644
index 000000000..3bf45155c
--- /dev/null
+++ b/src/3rdparty/python/lib/python3.9/site-packages/mac_alias/utils.py
@@ -0,0 +1,20 @@
+import datetime
+
+ZERO = datetime.timedelta(0)
+
+
+class UTC(datetime.tzinfo):
+ def utcoffset(self, dt):
+ return ZERO
+
+ def dst(self, dt):
+ return ZERO
+
+ def tzname(self, dt):
+ return "UTC"
+
+
+utc = UTC()
+mac_epoch = datetime.datetime(1904, 1, 1, 0, 0, 0, 0, utc)
+unix_epoch = datetime.datetime(1970, 1, 1, 0, 0, 0, 0, utc)
+osx_epoch = datetime.datetime(2001, 1, 1, 0, 0, 0, 0, utc)
diff --git a/src/app/config/configcommandexecutor.cpp b/src/app/config/configcommandexecutor.cpp
index 023514980..cbcb5ee73 100644
--- a/src/app/config/configcommandexecutor.cpp
+++ b/src/app/config/configcommandexecutor.cpp
@@ -49,11 +49,52 @@
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qtextstream.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
#include <cstdio>
using namespace qbs;
+static QJsonObject settingsToJSONObject(
+ Settings &settings, qbs::Settings::Scopes scopes, const QString &parentGroup = {})
+{
+ QJsonObject result;
+
+ const auto allKeys = settings.directChildren(parentGroup, scopes);
+ for (const auto& key : allKeys) {
+ const auto fullKey = parentGroup.isEmpty()
+ ? key
+ : QStringLiteral("%1.%2").arg(parentGroup, key);
+ const auto value = settings.value(fullKey, scopes);
+ if (value.isValid()) { // looks like a real value
+ result[key] = QJsonValue::fromVariant(value);
+ } else { // looks like a group
+ result[key] = settingsToJSONObject(settings, scopes, fullKey);
+ }
+ }
+
+ return result;
+}
+
+static void settingsFromJSONObject(
+ Settings &settings, const QJsonObject &object, const QString &parentGroup = {})
+{
+ for (auto it = object.begin(), end = object.end(); it != end; ++it) {
+ const auto key = it.key();
+ const auto value = it.value();
+ const auto fullKey = parentGroup.isEmpty()
+ ? key
+ : QStringLiteral("%1.%2").arg(parentGroup, key);
+ if (value.isObject()) {
+ settingsFromJSONObject(settings, it.value().toObject(), fullKey);
+ } else {
+ settings.setValue(fullKey, value.toVariant());
+ }
+ }
+}
+
+
ConfigCommandExecutor::ConfigCommandExecutor(Settings *settings, Settings::Scopes scope)
: m_settings(settings), m_scope(scope)
{
@@ -139,12 +180,20 @@ void ConfigCommandExecutor::exportSettings(const QString &filename)
throw ErrorInfo(tr("Could not open file '%1' for writing: %2")
.arg(QDir::toNativeSeparators(filename), file.errorString()));
}
- QTextStream stream(&file);
- setupDefaultCodec(stream);
- const auto keys = m_settings->allKeys(m_scope);
- for (const QString &key : keys)
- stream << key << ": " << qbs::settingsValueToRepresentation(m_settings->value(key, m_scope))
- << Qt::endl;
+
+ if (QFileInfo(filename).suffix() == u"json") {
+ QJsonDocument doc;
+ doc.setObject(settingsToJSONObject(*m_settings, m_scope));
+ file.write(doc.toJson());
+ } else {
+ QTextStream stream(&file);
+ setupDefaultCodec(stream);
+ const auto keys = m_settings->allKeys(m_scope);
+ for (const QString &key : keys)
+ stream << key << ": "
+ << qbs::settingsValueToRepresentation(m_settings->value(key, m_scope))
+ << Qt::endl;
+ }
}
void ConfigCommandExecutor::importSettings(const QString &filename)
@@ -159,15 +208,21 @@ void ConfigCommandExecutor::importSettings(const QString &filename)
for (const QString &key : keys)
m_settings->remove(key);
- QTextStream stream(&file);
- setupDefaultCodec(stream);
- while (!stream.atEnd()) {
- QString line = stream.readLine();
- int colon = line.indexOf(QLatin1Char(':'));
- if (colon >= 0 && !line.startsWith(QLatin1Char('#'))) {
- const QString key = line.left(colon).trimmed();
- const QString value = line.mid(colon + 1).trimmed();
- m_settings->setValue(key, representationToSettingsValue(value));
+ if (QFileInfo(filename).suffix() == u"json") {
+ const auto doc = QJsonDocument::fromJson(file.readAll());
+ const auto object = doc.object();
+ settingsFromJSONObject(*m_settings, doc.object());
+ } else {
+ QTextStream stream(&file);
+ setupDefaultCodec(stream);
+ while (!stream.atEnd()) {
+ QString line = stream.readLine();
+ int colon = line.indexOf(QLatin1Char(':'));
+ if (colon >= 0 && !line.startsWith(QLatin1Char('#'))) {
+ const QString key = line.left(colon).trimmed();
+ const QString value = line.mid(colon + 1).trimmed();
+ m_settings->setValue(key, representationToSettingsValue(value));
+ }
}
}
}
diff --git a/src/app/qbs-create-project/create-project-main.cpp b/src/app/qbs-create-project/create-project-main.cpp
index bb5d1a6bc..51e7ce514 100644
--- a/src/app/qbs-create-project/create-project-main.cpp
+++ b/src/app/qbs-create-project/create-project-main.cpp
@@ -80,9 +80,9 @@ int main(int argc, char *argv[])
const ProjectStructure projectStructure = parser.isSet(flatOpt)
? ProjectStructure::Flat : ProjectStructure::Composite;
const QStringList whiteList = parser.value(whiteListOpt).split(QLatin1Char(','),
- QBS_SKIP_EMPTY_PARTS);
+ Qt::SkipEmptyParts);
const QStringList blackList = parser.value(blackListOpt).split(QLatin1Char(','),
- QBS_SKIP_EMPTY_PARTS);
+ Qt::SkipEmptyParts);
try {
ProjectCreator().run(QDir::currentPath(), projectStructure, whiteList, blackList);
} catch (const ErrorInfo &e) {
diff --git a/src/app/qbs-setup-qt/main.cpp b/src/app/qbs-setup-qt/main.cpp
index bef95eee2..b739bff2d 100644
--- a/src/app/qbs-setup-qt/main.cpp
+++ b/src/app/qbs-setup-qt/main.cpp
@@ -82,7 +82,7 @@ int main(int argc, char *argv[])
QString profileName = QLatin1String("qt-") + qtEnvironment.qtVersion.toString();
if (SetupQt::checkIfMoreThanOneQtWithTheSameVersion(qtEnvironment.qtVersion, qtEnvironments)) {
QStringList prefixPathParts = QFileInfo(qtEnvironment.qmakeFilePath).path()
- .split(QLatin1Char('/'), QBS_SKIP_EMPTY_PARTS);
+ .split(QLatin1Char('/'), Qt::SkipEmptyParts);
if (!prefixPathParts.empty())
profileName += QLatin1String("-") + prefixPathParts.last();
}
diff --git a/src/app/qbs-setup-toolchains/clangclprobe.cpp b/src/app/qbs-setup-toolchains/clangclprobe.cpp
index 094f8aa14..272f953fc 100644
--- a/src/app/qbs-setup-toolchains/clangclprobe.cpp
+++ b/src/app/qbs-setup-toolchains/clangclprobe.cpp
@@ -121,13 +121,23 @@ void clangClProbe(Settings *settings, std::vector<Profile> &profiles)
return;
}
- const auto clangCl = clangCls.front();
const QString architectures[] = {
QStringLiteral("x86_64"),
QStringLiteral("x86")
};
- qbs::Internal::transform(architectures, profiles, [settings, clangCl](const auto &arch) {
- const auto profileName = QStringLiteral("clang-cl-%1").arg(arch);
- return createProfileHelper(settings, profileName, clangCl.toolchainInstallPath,
- clangCl.vcvarsallPath, arch); });
+ for (size_t index = 0; index < clangCls.size(); ++index) {
+ const auto &clangCl = clangCls[index];
+ const QString suffix = index == 0 ? QString() : QStringLiteral("-%1").arg(index);
+ qbs::Internal::transform(
+ architectures, profiles, [settings, clangCl, suffix](const auto &arch) {
+ const auto profileName = QStringLiteral("clang-cl") + suffix
+ + QStringLiteral("-%1").arg(arch);
+ return createProfileHelper(
+ settings,
+ profileName,
+ clangCl.toolchainInstallPath,
+ clangCl.vcvarsallPath,
+ arch);
+ });
+ }
}
diff --git a/src/app/qbs-setup-toolchains/xcodeprobe.cpp b/src/app/qbs-setup-toolchains/xcodeprobe.cpp
index 9be12d3fc..ea27e4143 100644
--- a/src/app/qbs-setup-toolchains/xcodeprobe.cpp
+++ b/src/app/qbs-setup-toolchains/xcodeprobe.cpp
@@ -163,7 +163,7 @@ void XcodeProbe::detectDeveloperPaths()
qbsInfo() << Tr::tr("Could not detect additional Xcode installations with /usr/bin/mdfind");
} else {
const auto paths = QString::fromLocal8Bit(launchServices.readAllStandardOutput())
- .split(QLatin1Char('\n'), QBS_SKIP_EMPTY_PARTS);
+ .split(QLatin1Char('\n'), Qt::SkipEmptyParts);
for (const QString &path : paths)
addDeveloperPath(path + QStringLiteral("/Contents/Developer"));
}
diff --git a/src/app/qbs/CMakeLists.txt b/src/app/qbs/CMakeLists.txt
index c190efccc..cd9240f98 100644
--- a/src/app/qbs/CMakeLists.txt
+++ b/src/app/qbs/CMakeLists.txt
@@ -7,6 +7,8 @@ set(SOURCES
consoleprogressobserver.h
ctrlchandler.cpp
ctrlchandler.h
+ lspserver.cpp
+ lspserver.h
main.cpp
qbstool.cpp
qbstool.h
@@ -43,7 +45,11 @@ add_qbs_app(qbs
"QBS_RELATIVE_LIBEXEC_PATH=\"${QBS_RELATIVE_LIBEXEC_PATH}\""
"QBS_RELATIVE_SEARCH_PATH=\"${QBS_RELATIVE_SEARCH_PATH}\""
"QBS_RELATIVE_PLUGINS_PATH=\"${QBS_RELATIVE_PLUGINS_PATH}\""
- DEPENDS qbscore qbsconsolelogger
+ DEPENDS
+ qbscore
+ qbsconsolelogger
+ qtclsp
+ Qt${QT_VERSION_MAJOR}::Network
SOURCES ${SOURCES} ${PARSER_SOURCES}
)
diff --git a/src/app/qbs/commandlinefrontend.cpp b/src/app/qbs/commandlinefrontend.cpp
index 3ed5cd1ad..4a4781207 100644
--- a/src/app/qbs/commandlinefrontend.cpp
+++ b/src/app/qbs/commandlinefrontend.cpp
@@ -152,7 +152,6 @@ void CommandLineFrontend::start()
params.setDryRun(m_parser.dryRun());
params.setForceProbeExecution(m_parser.forceProbesExecution());
params.setWaitLockBuildGraph(m_parser.waitLockBuildGraph());
- params.setFallbackProviderEnabled(!m_parser.disableFallbackProvider());
params.setLogElapsedTime(m_parser.logTime());
params.setSettingsDirectory(m_settings->baseDirectory());
params.setOverrideBuildGraphData(m_parser.command() == ResolveCommandType);
@@ -184,6 +183,7 @@ void CommandLineFrontend::start()
params.setConfigurationName(configurationName);
params.setBuildRoot(buildDirectory(profileName));
params.setOverriddenValues(userConfig);
+ params.setMaxJobCount(m_parser.jobCount(profileName));
SetupProjectJob * const job = Project().setupProject(params,
ConsoleLogger::instance().logSink(), this);
connectJob(job);
@@ -353,8 +353,8 @@ CommandLineFrontend::ProductMap CommandLineFrontend::productsToUse() const
QList<ProductData> &productList = products[project];
const ProjectData projectData = project.projectData();
for (const ProductData &product : projectData.allProducts()) {
- productNames << product.name();
- if (useAll || m_parser.products().contains(product.name())) {
+ productNames << product.fullDisplayName();
+ if (useAll || m_parser.products().contains(product.fullDisplayName())) {
productList.push_back(product);
}
}
@@ -565,7 +565,7 @@ int CommandLineFrontend::runTarget()
const QString executableFilePath = productToRun.targetExecutable();
if (executableFilePath.isEmpty()) {
throw ErrorInfo(Tr::tr("Cannot run: Product '%1' is not an application.")
- .arg(productToRun.name()));
+ .arg(productToRun.fullDisplayName()));
}
RunEnvironment runEnvironment = m_projects.front().getRunEnvironment(productToRun,
m_parser.installOptions(m_projects.front().profile()),
@@ -648,7 +648,7 @@ ProductData CommandLineFrontend::getTheOneRunnableProduct()
if (m_parser.products().size() == 1) {
for (const ProductData &p : m_projects.front().projectData().allProducts()) {
- if (p.name() == m_parser.products().constFirst())
+ if (p.fullDisplayName() == m_parser.products().constFirst())
return p;
}
QBS_CHECK(false);
@@ -672,14 +672,8 @@ ProductData CommandLineFrontend::getTheOneRunnableProduct()
ErrorInfo error(Tr::tr("Ambiguous use of command '%1': No product given, but project "
"has more than one runnable product.").arg(m_parser.commandName()));
error.append(Tr::tr("Use the '--products' option with one of the following products:"));
- for (const ProductData &p : std::as_const(runnableProducts)) {
- QString productRepr = QLatin1String("\t") + p.name();
- if (p.profile() != m_projects.front().profile()) {
- productRepr.append(QLatin1String(" [")).append(p.profile())
- .append(QLatin1Char(']'));
- }
- error.append(productRepr);
- }
+ for (const ProductData &p : std::as_const(runnableProducts))
+ error.append(QLatin1String("\t") + p.fullDisplayName());
throw error;
}
diff --git a/src/app/qbs/lspserver.cpp b/src/app/qbs/lspserver.cpp
new file mode 100644
index 000000000..c6cce6706
--- /dev/null
+++ b/src/app/qbs/lspserver.cpp
@@ -0,0 +1,629 @@
+/****************************************************************************
+**
+** 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 "lspserver.h"
+
+#include <api/projectdata.h>
+#include <logging/translator.h>
+#include <lsp/basemessage.h>
+#include <lsp/completion.h>
+#include <lsp/initializemessages.h>
+#include <lsp/jsonrpcmessages.h>
+#include <lsp/messages.h>
+#include <lsp/servercapabilities.h>
+#include <lsp/textsynchronization.h>
+#include <parser/qmljsastvisitor_p.h>
+#include <parser/qmljslexer_p.h>
+#include <parser/qmljsparser_p.h>
+#include <tools/qbsassert.h>
+#include <tools/stlutils.h>
+
+#include <QBuffer>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QMap>
+
+#include <unordered_map>
+#ifdef Q_OS_WINDOWS
+#include <process.h>
+#else
+#include <unistd.h>
+#endif
+
+namespace qbs::Internal {
+
+static int getPid()
+{
+#ifdef Q_OS_WINDOWS
+ return _getpid();
+#else
+ return getpid();
+#endif
+}
+
+using LspErrorResponse = lsp::ResponseError<std::nullptr_t>;
+using LspErrorCode = LspErrorResponse::ErrorCodes;
+
+class Document {
+public:
+ bool isPositionUpToDate(const CodePosition &pos) const;
+ bool isPositionUpToDate(const lsp::Position &pos) const;
+
+ QString savedContent;
+ QString currentContent;
+};
+
+static CodePosition posFromLspPos(const lsp::Position &pos)
+{
+ return {pos.line() + 1, pos.character() + 1};
+}
+
+static lsp::Position lspPosFromCodeLocation(const CodeLocation &loc)
+{
+ return {loc.line() - 1, loc.column() - 1};
+}
+
+static QString uriToString(const lsp::DocumentUri &uri)
+{
+ return uri.toFilePath([](const lsp::Utils::FilePath &fp) { return fp; });
+}
+
+static int posToOffset(const CodePosition &pos, const QString &doc);
+static int posToOffset(const lsp::Position &pos, const QString &doc)
+{
+ return posToOffset(posFromLspPos(pos), doc);
+}
+
+class AstNodeLocator : public QbsQmlJS::AST::Visitor
+{
+public:
+ AstNodeLocator(int position, QbsQmlJS::AST::UiProgram &ast)
+ : m_position(position)
+ {
+ ast.accept(this);
+ }
+
+ QList<QbsQmlJS::AST::Node *> path() const { return m_path; }
+
+private:
+ bool preVisit(QbsQmlJS::AST::Node *node) override
+ {
+ if (int(node->firstSourceLocation().offset) > m_position)
+ return false;
+ if (int(node->lastSourceLocation().offset) < m_position)
+ return false;
+ m_path << node;
+ return true;
+ }
+
+ const int m_position;
+ QList<QbsQmlJS::AST::Node *> m_path;
+};
+
+class LspServer::Private
+{
+public:
+ void setupConnection();
+ void handleIncomingData();
+ void discardSocket();
+ template<typename T> void sendResponse(const T &msg);
+ void sendErrorResponse(LspErrorCode code, const QString &message);
+ void sendErrorNotification(const QString &message);
+ void sendNoSuchDocumentError(const QString &filePath);
+ void sendMessage(const lsp::JsonObject &msg);
+ void sendMessage(const lsp::JsonRpcMessage &msg);
+ void handleCurrentMessage();
+ void handleInitializeRequest();
+ void handleInitializedNotification();
+ void handleGotoDefinitionRequest();
+ void handleCompletionRequest();
+ void handleShutdownRequest();
+ void handleDidOpenNotification();
+ void handleDidChangeNotification();
+ void handleDidSaveNotification();
+ void handleDidCloseNotification();
+
+ QLocalServer server;
+ QBuffer incomingData;
+ lsp::BaseMessage currentMessage;
+ QJsonObject messageObject;
+ QLocalSocket *socket = nullptr;
+ ProjectData projectData;
+ CodeLinks codeLinks;
+ std::unordered_map<QString, Document> documents;
+
+ enum class State { None, InitRequest, InitNotification, Shutdown };
+ State state = State::None;
+};
+
+LspServer::LspServer() : d(new Private)
+{
+ if (!d->server.listen(QString::fromLatin1("qbs-lsp-%1").arg(getPid()))) {
+ // This is not fatal, qbs main operation can continue.
+ qWarning() << "failed to open lsp socket:" << d->server.errorString();
+ return;
+ }
+ QObject::connect(&d->server, &QLocalServer::newConnection, [this] {
+ d->socket = d->server.nextPendingConnection();
+ d->setupConnection();
+ d->server.close();
+ });
+}
+
+LspServer::~LspServer() { delete d; }
+
+void LspServer::updateProjectData(const ProjectData &projectData, const CodeLinks &codeLinks)
+{
+ d->projectData = projectData;
+ d->codeLinks = codeLinks;
+}
+
+QString LspServer::socketPath() const { return d->server.fullServerName(); }
+
+void LspServer::Private::setupConnection()
+{
+ QBS_ASSERT(socket, return);
+
+ QObject::connect(socket, &QLocalSocket::errorOccurred, [this] { discardSocket(); });
+ QObject::connect(socket, &QLocalSocket::disconnected, [this] { discardSocket(); });
+ QObject::connect(socket, &QLocalSocket::readyRead, [this] { handleIncomingData(); });
+ incomingData.open(QIODevice::ReadWrite | QIODevice::Append);
+ handleIncomingData();
+}
+
+void LspServer::Private::handleIncomingData()
+{
+ const int pos = incomingData.pos();
+ incomingData.write(socket->readAll());
+ incomingData.seek(pos);
+ QString parseError;
+ lsp::BaseMessage::parse(&incomingData, parseError, currentMessage);
+ if (!parseError.isEmpty())
+ return sendErrorResponse(LspErrorResponse::ParseError, parseError);
+ if (currentMessage.isComplete()) {
+ incomingData.buffer().remove(0, incomingData.pos());
+ incomingData.seek(0);
+ handleCurrentMessage();
+ currentMessage = {};
+ messageObject = {};
+ if (socket)
+ handleIncomingData();
+ }
+}
+
+void LspServer::Private::discardSocket()
+{
+ socket->disconnect();
+ socket->deleteLater();
+ socket = nullptr;
+}
+
+template<typename T> void LspServer::Private::sendResponse(const T &msg)
+{
+ lsp::Response<T, std::nullptr_t> response(lsp::MessageId(messageObject.value(lsp::idKey)));
+ response.setResult(msg);
+ sendMessage(response);
+}
+
+void LspServer::Private::sendErrorResponse(LspErrorCode code, const QString &message)
+{
+ lsp::Response<lsp::JsonObject, std::nullptr_t> response(lsp::MessageId(
+ messageObject.value(lsp::idKey)));
+ lsp::ResponseError<std::nullptr_t> error;
+ error.setCode(code);
+ error.setMessage(message);
+ response.setError(error);
+ sendMessage(response);
+}
+
+void LspServer::Private::sendErrorNotification(const QString &message)
+{
+ lsp::ShowMessageParams params;
+ params.setType(lsp::Error);
+ params.setMessage(message);
+ sendMessage(lsp::ShowMessageNotification(params));
+}
+
+void LspServer::Private::sendNoSuchDocumentError(const QString &filePath)
+{
+ sendErrorNotification(Tr::tr("No such document: '%1'").arg(filePath));
+}
+
+void LspServer::Private::sendMessage(const lsp::JsonObject &msg)
+{
+ sendMessage(lsp::JsonRpcMessage(msg));
+}
+
+void LspServer::Private::sendMessage(const lsp::JsonRpcMessage &msg)
+{
+ lsp::BaseMessage baseMsg = msg.toBaseMessage();
+ socket->write(baseMsg.header());
+ socket->write(baseMsg.content);
+}
+
+void LspServer::Private::handleCurrentMessage()
+{
+ messageObject = lsp::JsonRpcMessage(currentMessage).toJsonObject();
+ const QString method = messageObject.value(lsp::methodKey).toString();
+ if (method == QLatin1String("exit"))
+ return discardSocket();
+ if (state == State::Shutdown) {
+ return sendErrorResponse(LspErrorResponse::InvalidRequest,
+ Tr::tr("Method '%1' not allowed after shutdown."));
+ }
+ if (method == "shutdown")
+ return handleShutdownRequest();
+ if (method == QLatin1String("initialize"))
+ return handleInitializeRequest();
+ if (state == State::None) {
+ return sendErrorResponse(LspErrorResponse::ServerNotInitialized,
+ Tr::tr("First message must be initialize request."));
+ }
+ if (method == "initialized")
+ return handleInitializedNotification();
+ if (method == "textDocument/didOpen")
+ return handleDidOpenNotification();
+ if (method == "textDocument/didChange")
+ return handleDidChangeNotification();
+ if (method == "textDocument/didSave")
+ return handleDidSaveNotification();
+ if (method == "textDocument/didClose")
+ return handleDidCloseNotification();
+ if (method == "textDocument/definition")
+ return handleGotoDefinitionRequest();
+ if (method == "textDocument/completion")
+ return handleCompletionRequest();
+
+ sendErrorResponse(LspErrorResponse::MethodNotFound, Tr::tr("This server can do very little."));
+}
+
+void LspServer::Private::handleInitializeRequest()
+{
+ if (state != State::None) {
+ return sendErrorResponse(LspErrorResponse::InvalidRequest,
+ Tr::tr("Received initialize request in initialized state."));
+ }
+ state = State::InitRequest;
+
+ lsp::ServerInfo serverInfo;
+ serverInfo.insert(lsp::nameKey, "qbs");
+ serverInfo.insert(lsp::versionKey, QBS_VERSION);
+ lsp::InitializeResult result;
+ result.insert(lsp::serverInfoKey, serverInfo);
+ lsp::ServerCapabilities capabilities; // TODO: hover
+ capabilities.setDefinitionProvider(true);
+ capabilities.setTextDocumentSync({int(lsp::TextDocumentSyncKind::Incremental)});
+ lsp::ServerCapabilities::CompletionOptions completionOptions;
+ completionOptions.setTriggerCharacters({"."});
+ capabilities.setCompletionProvider(completionOptions);
+ result.setCapabilities(capabilities);
+ sendResponse(result);
+}
+
+void LspServer::Private::handleInitializedNotification()
+{
+ if (state != State::InitRequest) {
+ return sendErrorResponse(LspErrorResponse::InvalidRequest,
+ Tr::tr("Unexpected initialized notification."));
+ }
+ state = State::InitNotification;
+}
+
+void LspServer::Private::handleGotoDefinitionRequest()
+{
+ const lsp::TextDocumentPositionParams params(messageObject.value(lsp::paramsKey));
+ const QString sourceFile = params.textDocument().uri().toLocalFile();
+ const Document *sourceDoc = nullptr;
+ if (const auto it = documents.find(sourceFile); it != documents.end())
+ sourceDoc = &it->second;
+ const auto fileEntry = codeLinks.constFind(sourceFile);
+ if (fileEntry == codeLinks.constEnd())
+ return sendResponse(nullptr);
+ const CodePosition sourcePos = posFromLspPos(params.position());
+ if (sourceDoc && !sourceDoc->isPositionUpToDate(sourcePos))
+ return sendResponse(nullptr);
+ for (auto it = fileEntry->cbegin(); it != fileEntry->cend(); ++it) {
+ if (!it.key().contains(sourcePos))
+ continue;
+ if (sourceDoc && !sourceDoc->isPositionUpToDate(it.key().end()))
+ return sendResponse(nullptr);
+ QList<CodeLocation> targets = it.value();
+ QBS_ASSERT(!targets.isEmpty(), return sendResponse(nullptr));
+ for (auto it = targets.begin(); it != targets.end();) {
+ const Document *targetDoc = nullptr;
+ if (it->filePath() == sourceFile)
+ targetDoc = sourceDoc;
+ else if (const auto docIt = documents.find(it->filePath()); docIt != documents.end())
+ targetDoc = &docIt->second;
+ if (targetDoc && !targetDoc->isPositionUpToDate(CodePosition(it->line(), it->column())))
+ it = targets.erase(it);
+ else
+ ++it;
+ }
+ struct JsonArray : public QJsonArray { void reserve(std::size_t) {}};
+ const auto locations = transformed<JsonArray>(targets,
+ [](const CodeLocation &loc) {
+ const lsp::Position startPos = lspPosFromCodeLocation(loc);
+ const lsp::Position endPos(startPos.line(), startPos.character() + 1);
+ lsp::Location targetLocation;
+ targetLocation.setUri(lsp::DocumentUri::fromProtocol(
+ QUrl::fromLocalFile(loc.filePath()).toString()));
+ targetLocation.setRange({startPos, endPos});
+ return QJsonObject(targetLocation);
+ });
+ if (locations.size() == 1)
+ return sendResponse(locations.first().toObject());
+ return sendResponse(locations);
+ }
+ sendResponse(nullptr);
+}
+
+// We operate under the assumption that the client has basic QML support.
+// Therefore, we only provide completion for qbs modules and their properties.
+// Only a simple prefix match is implemented, with no regard to the contents after the cursor.
+void LspServer::Private::handleCompletionRequest()
+{
+ if (!projectData.isValid())
+ return sendResponse(nullptr);
+
+ const lsp::CompletionParams params(messageObject.value(lsp::paramsKey));
+ const QString sourceFile = params.textDocument().uri().toLocalFile();
+ const Document *sourceDoc = nullptr;
+ if (const auto it = documents.find(sourceFile); it != documents.end())
+ sourceDoc = &it->second;
+ if (!sourceDoc)
+ return sendResponse(nullptr);
+
+ // If there are products corresponding to this file, check only these when looking for modules.
+ // Otherwise, check all products.
+ const QList<ProductData> allProducts = projectData.allProducts();
+ if (allProducts.isEmpty())
+ return sendResponse(nullptr);
+ QList<ProductData> relevantProducts;
+ for (const ProductData &p : allProducts) {
+ if (p.location().filePath() == sourceFile)
+ relevantProducts << p;
+ }
+ if (relevantProducts.isEmpty())
+ relevantProducts = allProducts;
+
+ QString identifierPrefix;
+ QStringList modulePrefixes;
+ const int offset = posToOffset(params.position(), sourceDoc->currentContent) - 1;
+ if (offset < 0 || offset >= sourceDoc->currentContent.length())
+ return sendResponse(nullptr);
+ const auto collectFromRawString = [&] {
+ int currentPos = offset;
+ const auto constructIdentifier = [&] {
+ QString id;
+ while (currentPos >= 0) {
+ const QChar c = sourceDoc->currentContent.at(currentPos);
+ if (!c.isLetterOrNumber() && c != '_')
+ break;
+ id.prepend(c);
+ --currentPos;
+ }
+ return id;
+ };
+ identifierPrefix = constructIdentifier();
+ while (true) {
+ if (currentPos <= 0 || sourceDoc->currentContent.at(currentPos) != '.')
+ return;
+ --currentPos;
+ const QString modulePrefix = constructIdentifier();
+ if (modulePrefix.isEmpty())
+ return;
+ modulePrefixes.prepend(modulePrefix);
+ }
+ };
+
+ // Parse the current file. Note that completion usually happens on invalid code, which
+ // often confuses the parser so much that it yields unusable results. Therefore, we always
+ // gather our input parameters from the raw string. We only use the parse result to skip
+ // completion in contexts where it is undesirable.
+ QbsQmlJS::Engine engine;
+ QbsQmlJS::Lexer lexer(&engine);
+ lexer.setCode(sourceDoc->currentContent, 1);
+ QbsQmlJS::Parser parser(&engine);
+ parser.parse();
+ if (parser.ast()) {
+ AstNodeLocator locator(offset, *parser.ast());
+ const QList<QbsQmlJS::AST::Node *> &astPath = locator.path();
+ if (!astPath.isEmpty()) {
+ switch (astPath.last()->kind) {
+ case QbsQmlJS::AST::Node::Kind_FieldMemberExpression:
+ case QbsQmlJS::AST::Node::Kind_UiObjectDefinition:
+ case QbsQmlJS::AST::Node::Kind_UiQualifiedId:
+ case QbsQmlJS::AST::Node::Kind_UiScriptBinding:
+ break;
+ default:
+ return sendResponse(nullptr);
+ }
+ }
+ }
+
+ collectFromRawString();
+ if (modulePrefixes.isEmpty() && identifierPrefix.isEmpty())
+ return sendResponse(nullptr); // We do not want to start completion from nothing.
+
+ QJsonArray results;
+ QMap<QString, QString> namesAndTypes;
+ for (const ProductData &product : std::as_const(relevantProducts)) {
+ const PropertyMap &moduleProps = product.moduleProperties();
+ const QStringList allModules = moduleProps.allModules();
+ const QString moduleNameOrPrefix = modulePrefixes.join('.');
+
+ // Case 1: Prefixes match a module name. Identifier can only expand to the name
+ // of a module property.
+ // Example: "Qt.core.a^" -> "Qt.core.availableBuildVariants"
+ if (!modulePrefixes.isEmpty() && allModules.contains(moduleNameOrPrefix)) {
+ for (const PropertyMap::PropertyInfo &info :
+ moduleProps.allPropertiesForModule(moduleNameOrPrefix)) {
+ if (info.isBuiltin)
+ continue;
+ if (!identifierPrefix.isEmpty() && !info.name.startsWith(identifierPrefix))
+ continue;
+ namesAndTypes.insert(info.name, info.type);
+ }
+ continue;
+ }
+
+ // Case 2: Isolated identifier. Can only expand to a module name.
+ // Example: "Q^" -> "Qt.core", "Qt.widgets", ...
+ // Case 3: Prefixes match a module prefix. Identifier can only expand to a module name.
+ // Example: "Qt.c^" -> "Qt.core", "Qt.concurrent", ...
+ QString fullPrefix = identifierPrefix;
+ int nameOffset = 0;
+ if (!modulePrefixes.isEmpty()) {
+ fullPrefix.prepend(moduleNameOrPrefix + '.');
+ nameOffset = moduleNameOrPrefix.length() + 1;
+ }
+ for (const QString &module : allModules) {
+ if (module.startsWith(fullPrefix))
+ namesAndTypes.insert(module.mid(nameOffset), {});
+ }
+ }
+
+ for (auto it = namesAndTypes.cbegin(); it != namesAndTypes.cend(); ++it) {
+ lsp::CompletionItem item;
+ item.setLabel(it.key());
+ if (!it.value().isEmpty())
+ item.setDetail(it.value());
+ results.append(QJsonObject(item));
+ };
+ sendResponse(results);
+}
+
+void LspServer::Private::handleShutdownRequest()
+{
+ state = State::Shutdown;
+ sendResponse(nullptr);
+}
+
+void LspServer::Private::handleDidOpenNotification()
+{
+ const lsp::TextDocumentItem docItem = lsp::DidOpenTextDocumentNotification(messageObject)
+ .params().value_or(lsp::DidOpenTextDocumentParams())
+ .textDocument();
+ if (!docItem.isValid())
+ return sendErrorNotification(Tr::tr("Invalid didOpen parameters."));
+ const QString filePath = uriToString(docItem.uri());
+ Document &doc = documents[filePath];
+ doc.savedContent = doc.currentContent = docItem.text();
+}
+
+void LspServer::Private::handleDidChangeNotification()
+{
+ const auto params = lsp::DidChangeTextDocumentNotification(messageObject)
+ .params().value_or(lsp::DidChangeTextDocumentParams());
+ if (!params.isValid())
+ return sendErrorNotification(Tr::tr("Invalid didChange parameters."));
+ const QString filePath = uriToString(params.textDocument().uri());
+ const auto docIt = documents.find(filePath);
+ if (docIt == documents.end())
+ return sendNoSuchDocumentError(filePath);
+ Document &doc = docIt->second;
+ const auto changes = params.contentChanges();
+ for (const auto &change : changes) {
+ const auto range = change.range();
+ if (!range) {
+ doc.currentContent = change.text();
+ continue;
+ }
+ const int startPos = posToOffset(range->start(), doc.currentContent);
+ const int endPos = posToOffset(range->end(), doc.currentContent);
+ if (startPos == -1 || endPos == -1 || startPos > endPos)
+ return sendErrorResponse(LspErrorResponse::InvalidParams, Tr::tr("Invalid range."));
+ doc.currentContent.replace(startPos, endPos - startPos, change.text());
+ }
+}
+
+void LspServer::Private::handleDidSaveNotification()
+{
+ const QString filePath = uriToString(lsp::DidSaveTextDocumentNotification(messageObject)
+ .params().value_or(lsp::DidSaveTextDocumentParams())
+ .textDocument().uri());
+ const auto docIt = documents.find(filePath);
+ if (docIt == documents.end())
+ return sendNoSuchDocumentError(filePath);
+ docIt->second.savedContent = docIt->second.currentContent;
+
+ // TODO: Mark the project data as out of date until the next update(),if the document
+ // is in buildSystemFiles().
+}
+
+void LspServer::Private::handleDidCloseNotification()
+{
+ const QString filePath = uriToString(lsp::DidCloseTextDocumentNotification(messageObject)
+ .params().value_or(lsp::DidCloseTextDocumentParams())
+ .textDocument().uri());
+ const auto docIt = documents.find(filePath);
+ if (docIt == documents.end())
+ return sendNoSuchDocumentError(filePath);
+ documents.erase(docIt);
+}
+
+static int posToOffset(const CodePosition &pos, const QString &doc)
+{
+ int offset = 0;
+ for (int newlines = 0, next = 0; newlines < pos.line() - 1; ++newlines) {
+ offset = doc.indexOf('\n', next);
+ if (offset == -1)
+ return -1;
+ next = offset + 1;
+ }
+ return offset + pos.column();
+}
+
+bool Document::isPositionUpToDate(const CodePosition &pos) const
+{
+ const int origOffset = posToOffset(pos, savedContent);
+ if (origOffset > int(currentContent.size()))
+ return false;
+ return QStringView(currentContent).left(origOffset)
+ == QStringView(savedContent).left(origOffset);
+}
+
+bool Document::isPositionUpToDate(const lsp::Position &pos) const
+{
+ return isPositionUpToDate(posFromLspPos(pos));
+}
+
+} // namespace qbs::Internal
+
diff --git a/src/lib/corelib/loader/projecttreebuilder.h b/src/app/qbs/lspserver.h
index a4f08a1f3..566808309 100644
--- a/src/lib/corelib/loader/projecttreebuilder.h
+++ b/src/app/qbs/lspserver.h
@@ -39,53 +39,26 @@
#pragma once
-#include "loaderutils.h"
-
-#include <language/forward_decls.h>
-#include <language/moduleproviderinfo.h>
-#include <language/qualifiedid.h>
-#include <tools/pimpl.h>
+#include <tools/codelocation.h>
#include <QString>
-#include <QVariant>
namespace qbs {
-class SetupProjectParameters;
+class ProjectData;
namespace Internal {
-class Evaluator;
-class FileTime;
-class Item;
-class ItemPool;
-class ProgressObserver;
-class ProjectTreeBuilder
+class LspServer
{
public:
- ProjectTreeBuilder(const SetupProjectParameters &parameters, ItemPool &itemPool,
- Evaluator &evaluator, Logger &logger);
- ~ProjectTreeBuilder();
-
- struct Result
- {
- Item *root = nullptr;
- std::unordered_map<Item *, ProductInfo> productInfos;
- std::vector<ProbeConstPtr> projectProbes;
- StoredModuleProviderInfo storedModuleProviderInfo;
- Set<QString> qbsFiles;
- QVariantMap profileConfigs;
- };
- Result load();
+ LspServer();
+ ~LspServer();
- void setProgressObserver(ProgressObserver *progressObserver);
- void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
- void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void setLastResolveTime(const FileTime &time);
- void setStoredProfiles(const QVariantMap &profiles);
- void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
+ void updateProjectData(const ProjectData &projectData, const CodeLinks &codeLinks);
+ QString socketPath() const;
private:
class Private;
- Pimpl<Private> d;
+ Private * const d;
};
} // namespace Internal
diff --git a/src/app/qbs/parser/commandlineoption.cpp b/src/app/qbs/parser/commandlineoption.cpp
index 389b304f0..7572b4e66 100644
--- a/src/app/qbs/parser/commandlineoption.cpp
+++ b/src/app/qbs/parser/commandlineoption.cpp
@@ -710,17 +710,6 @@ QString WaitLockOption::longRepresentation() const
return QStringLiteral("--wait-lock");
}
-QString DisableFallbackProviderOption::description(CommandType) const
-{
- return Tr::tr("%1\n\tDo not fall back to pkg-config if a dependency is not found.\n")
- .arg(longRepresentation());
-}
-
-QString DisableFallbackProviderOption::longRepresentation() const
-{
- return QStringLiteral("--no-fallback-module-provider");
-}
-
QString RunEnvConfigOption::description(CommandType command) const
{
Q_UNUSED(command);
diff --git a/src/app/qbs/parser/commandlineoption.h b/src/app/qbs/parser/commandlineoption.h
index d51d5f765..7b8baeae2 100644
--- a/src/app/qbs/parser/commandlineoption.h
+++ b/src/app/qbs/parser/commandlineoption.h
@@ -55,7 +55,9 @@ public:
enum Type {
FileOptionType,
BuildDirectoryOptionType,
- LogLevelOptionType, VerboseOptionType, QuietOptionType,
+ LogLevelOptionType,
+ VerboseOptionType,
+ QuietOptionType,
JobsOptionType,
KeepGoingOptionType,
DryRunOptionType,
@@ -64,7 +66,9 @@ public:
ChangedFilesOptionType,
ProductsOptionType,
NoInstallOptionType,
- InstallRootOptionType, RemoveFirstOptionType, NoBuildOptionType,
+ InstallRootOptionType,
+ RemoveFirstOptionType,
+ NoBuildOptionType,
ForceTimestampCheckOptionType,
ForceOutputCheckOptionType,
BuildNonDefaultOptionType,
@@ -76,7 +80,6 @@ public:
GeneratorOptionType,
WaitLockOptionType,
RunEnvConfigOptionType,
- DisableFallbackProviderType,
DeprecationWarningsOptionType,
};
@@ -431,14 +434,6 @@ public:
QString longRepresentation() const override;
};
-class DisableFallbackProviderOption : public OnOffOption
-{
-public:
- QString description(CommandType command) const override;
- QString shortRepresentation() const override { return {}; }
- QString longRepresentation() const override;
-};
-
} // namespace qbs
#endif // QBS_COMMANDLINEOPTION_H
diff --git a/src/app/qbs/parser/commandlineoptionpool.cpp b/src/app/qbs/parser/commandlineoptionpool.cpp
index 3908f262c..692c9c737 100644
--- a/src/app/qbs/parser/commandlineoptionpool.cpp
+++ b/src/app/qbs/parser/commandlineoptionpool.cpp
@@ -128,9 +128,6 @@ CommandLineOption *CommandLineOptionPool::getOption(CommandLineOption::Type type
case CommandLineOption::WaitLockOptionType:
option = new WaitLockOption;
break;
- case CommandLineOption::DisableFallbackProviderType:
- option = new DisableFallbackProviderOption;
- break;
case CommandLineOption::RunEnvConfigOptionType:
option = new RunEnvConfigOption;
break;
@@ -279,12 +276,6 @@ WaitLockOption *CommandLineOptionPool::waitLockOption() const
return static_cast<WaitLockOption *>(getOption(CommandLineOption::WaitLockOptionType));
}
-DisableFallbackProviderOption *CommandLineOptionPool::disableFallbackProviderOption() const
-{
- return static_cast<DisableFallbackProviderOption *>(
- getOption(CommandLineOption::DisableFallbackProviderType));
-}
-
RunEnvConfigOption *CommandLineOptionPool::runEnvConfigOption() const
{
return static_cast<RunEnvConfigOption *>(getOption(CommandLineOption::RunEnvConfigOptionType));
diff --git a/src/app/qbs/parser/commandlineoptionpool.h b/src/app/qbs/parser/commandlineoptionpool.h
index 5f3c1d87c..022e9fd09 100644
--- a/src/app/qbs/parser/commandlineoptionpool.h
+++ b/src/app/qbs/parser/commandlineoptionpool.h
@@ -77,7 +77,6 @@ public:
RespectProjectJobLimitsOption *respectProjectJobLimitsOption() const;
GeneratorOption *generatorOption() const;
WaitLockOption *waitLockOption() const;
- DisableFallbackProviderOption *disableFallbackProviderOption() const;
RunEnvConfigOption *runEnvConfigOption() const;
DeprecationWarningsOption *deprecationWarningsOption() const;
diff --git a/src/app/qbs/parser/commandlineparser.cpp b/src/app/qbs/parser/commandlineparser.cpp
index 14e26ca42..c548cf2b5 100644
--- a/src/app/qbs/parser/commandlineparser.cpp
+++ b/src/app/qbs/parser/commandlineparser.cpp
@@ -65,6 +65,7 @@
#include <QtCore/qmap.h>
#include <QtCore/qtextstream.h>
+#include <algorithm>
#include <utility>
#ifdef Q_OS_UNIX
@@ -155,17 +156,11 @@ QString CommandLineParser::projectBuildDirectory() const
BuildOptions CommandLineParser::buildOptions(const QString &profile) const
{
- Settings settings(settingsDir());
- Preferences preferences(&settings, profile);
-
- if (d->buildOptions.maxJobCount() <= 0) {
- d->buildOptions.setMaxJobCount(preferences.jobs());
- }
-
+ d->buildOptions.setMaxJobCount(jobCount(profile));
if (d->buildOptions.echoMode() < 0) {
- d->buildOptions.setEchoMode(preferences.defaultEchoMode());
+ Settings settings(settingsDir());
+ d->buildOptions.setEchoMode(Preferences(&settings, profile).defaultEchoMode());
}
-
return d->buildOptions;
}
@@ -202,6 +197,15 @@ InstallOptions CommandLineParser::installOptions(const QString &profile) const
return options;
}
+int CommandLineParser::jobCount(const QString &profile) const
+{
+ if (const int explicitJobCount = d->optionPool.jobsOption()->jobCount(); explicitJobCount > 0)
+ return explicitJobCount;
+
+ Settings settings(settingsDir());
+ return Preferences(&settings, profile).jobs();
+}
+
bool CommandLineParser::forceTimestampCheck() const
{
return d->optionPool.forceTimestampCheckOption()->enabled();
@@ -227,11 +231,6 @@ bool CommandLineParser::waitLockBuildGraph() const
return d->optionPool.waitLockOption()->enabled();
}
-bool CommandLineParser::disableFallbackProvider() const
-{
- return d->optionPool.disableFallbackProviderOption()->enabled();
-}
-
bool CommandLineParser::logTime() const
{
return d->logTime;
@@ -334,7 +333,19 @@ void CommandLineParser::CommandLineParserPrivate::doParse()
} else {
command = commandFromString(commandLine.front());
if (command) {
- commandLine.removeFirst();
+ const QString commandName = commandLine.takeFirst();
+
+ // if the command line contains a `<command>` with
+ // either `-h` or `--help` switch, we transform
+ // it to corresponding `help <command>` instead
+ const QStringList helpSwitches = {QStringLiteral("-h"), QStringLiteral("--help")};
+ if (auto it = std::find_first_of(
+ commandLine.begin(), commandLine.end(),
+ helpSwitches.begin(), helpSwitches.end());
+ it != commandLine.end()) {
+ command = commandPool.getCommand(HelpCommandType);
+ commandLine = QList{commandName}; // keep only command's name
+ }
} else { // No command given.
if (commandLine.front() == QLatin1String("-h")
|| commandLine.front() == QLatin1String("--help")) {
diff --git a/src/app/qbs/parser/commandlineparser.h b/src/app/qbs/parser/commandlineparser.h
index 999027006..4df8829a2 100644
--- a/src/app/qbs/parser/commandlineparser.h
+++ b/src/app/qbs/parser/commandlineparser.h
@@ -75,12 +75,12 @@ public:
CleanOptions cleanOptions(const QString &profile) const;
GenerateOptions generateOptions() const;
InstallOptions installOptions(const QString &profile) const;
+ int jobCount(const QString &profile) const;
bool forceTimestampCheck() const;
bool forceOutputCheck() const;
bool dryRun() const;
bool forceProbesExecution() const;
bool waitLockBuildGraph() const;
- bool disableFallbackProvider() const;
bool logTime() const;
bool withNonDefaultProducts() const;
bool buildBeforeInstalling() const;
diff --git a/src/app/qbs/parser/parsercommand.cpp b/src/app/qbs/parser/parsercommand.cpp
index bbb5db3d1..ef8da9551 100644
--- a/src/app/qbs/parser/parsercommand.cpp
+++ b/src/app/qbs/parser/parsercommand.cpp
@@ -199,17 +199,18 @@ QString ResolveCommand::representation() const
static QList<CommandLineOption::Type> resolveOptions()
{
- return {CommandLineOption::FileOptionType,
- CommandLineOption::BuildDirectoryOptionType,
- CommandLineOption::LogLevelOptionType,
- CommandLineOption::VerboseOptionType,
- CommandLineOption::QuietOptionType,
- CommandLineOption::ShowProgressOptionType,
- CommandLineOption::DryRunOptionType,
- CommandLineOption::ForceProbesOptionType,
- CommandLineOption::LogTimeOptionType,
- CommandLineOption::DeprecationWarningsOptionType,
- CommandLineOption::DisableFallbackProviderType};
+ return {
+ CommandLineOption::FileOptionType,
+ CommandLineOption::BuildDirectoryOptionType,
+ CommandLineOption::LogLevelOptionType,
+ CommandLineOption::VerboseOptionType,
+ CommandLineOption::QuietOptionType,
+ CommandLineOption::ShowProgressOptionType,
+ CommandLineOption::DryRunOptionType,
+ CommandLineOption::ForceProbesOptionType,
+ CommandLineOption::LogTimeOptionType,
+ CommandLineOption::DeprecationWarningsOptionType,
+ CommandLineOption::JobsOptionType};
}
QList<CommandLineOption::Type> ResolveCommand::supportedOptions() const
@@ -279,7 +280,6 @@ static QList<CommandLineOption::Type> buildOptions()
<< CommandLineOption::ForceTimestampCheckOptionType
<< CommandLineOption::ForceOutputCheckOptionType
<< CommandLineOption::BuildNonDefaultOptionType
- << CommandLineOption::JobsOptionType
<< CommandLineOption::CommandEchoModeOptionType
<< CommandLineOption::NoInstallOptionType
<< CommandLineOption::RemoveFirstOptionType
diff --git a/src/app/qbs/qbs.qbs b/src/app/qbs/qbs.qbs
index 03685dd21..52c29e9b2 100644
--- a/src/app/qbs/qbs.qbs
+++ b/src/app/qbs/qbs.qbs
@@ -2,12 +2,16 @@ import qbs.Utilities
QbsApp {
name: "qbs_app"
- Depends { name: "qbs resources" }
targetName: "qbs"
+
+ Depends { name: "qbs resources" }
+ Depends { name: "qtclsp" }
+ Depends { name: "Qt.network" }
Depends {
condition: Qt.core.staticBuild || qbsbuildconfig.staticBuild
productTypes: ["qbsplugin"]
}
+
cpp.defines: base.concat([
"QBS_VERSION=" + Utilities.cStringQuote(qbsversion.version),
"QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath),
@@ -54,5 +58,13 @@ QbsApp {
"parsercommand.h",
]
}
+ Group {
+ name: "lsp"
+ cpp.defines: outer.filter(function(d) { return d !== "QT_NO_CAST_FROM_ASCII"; })
+ files: [
+ "lspserver.cpp",
+ "lspserver.h",
+ ]
+ }
}
diff --git a/src/app/qbs/session.cpp b/src/app/qbs/session.cpp
index c958c88b6..2cdcf2b63 100644
--- a/src/app/qbs/session.cpp
+++ b/src/app/qbs/session.cpp
@@ -39,6 +39,7 @@
#include "session.h"
+#include "lspserver.h"
#include "sessionpacket.h"
#include "sessionpacketreader.h"
@@ -66,7 +67,6 @@
#include <QtCore/qobject.h>
#include <QtCore/qprocess.h>
-#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <memory>
@@ -166,6 +166,7 @@ private:
FileUpdateData prepareFileUpdate(const QJsonObject &request);
SessionPacketReader m_packetReader;
+ LspServer m_lspServer;
Project m_project;
ProjectData m_projectData;
SessionLogSink m_logSink;
@@ -193,7 +194,7 @@ Session::Session()
qApp->exit(EXIT_FAILURE);
}
#endif
- sendPacket(SessionPacket::helloMessage());
+ sendPacket(SessionPacket::helloMessage(m_lspServer.socketPath()));
connect(&m_logSink, &SessionLogSink::newMessage, this, &Session::sendPacket);
connect(&m_packetReader, &SessionPacketReader::errorOccurred,
this, [](const QString &msg) {
@@ -287,6 +288,7 @@ void Session::setupProject(const QJsonObject &request)
const ProjectData oldProjectData = m_projectData;
m_project = setupJob->project();
m_projectData = m_project.projectData();
+ m_lspServer.updateProjectData(m_projectData, m_project.codeLinks());
QJsonObject reply;
reply.insert(StringConstants::type(), QLatin1String("project-resolved"));
if (success)
@@ -621,10 +623,11 @@ Session::ProductSelection Session::getProductSelection(const QJsonObject &reques
{
const QJsonValue productSelection = request.value(StringConstants::productsKey());
if (productSelection.isArray())
- return ProductSelection(getProductsByName(fromJson<QStringList>(productSelection)));
- return ProductSelection(productSelection.toString() == QLatin1String("all")
- ? Project::ProductSelectionWithNonDefault
- : Project::ProductSelectionDefaultOnly);
+ return {getProductsByName(fromJson<QStringList>(productSelection))};
+ return {
+ productSelection.toString() == QLatin1String("all")
+ ? Project::ProductSelectionWithNonDefault
+ : Project::ProductSelectionDefaultOnly};
}
Session::FileUpdateData Session::prepareFileUpdate(const QJsonObject &request)
diff --git a/src/app/qbs/sessionpacket.cpp b/src/app/qbs/sessionpacket.cpp
index 3830704fa..470e27091 100644
--- a/src/app/qbs/sessionpacket.cpp
+++ b/src/app/qbs/sessionpacket.cpp
@@ -95,12 +95,13 @@ QByteArray SessionPacket::createPacket(const QJsonObject &packet)
.append(jsonData);
}
-QJsonObject SessionPacket::helloMessage()
+QJsonObject SessionPacket::helloMessage(const QString &lspSocket)
{
return QJsonObject{
{StringConstants::type(), QLatin1String("hello")},
- {QLatin1String("api-level"), 3},
- {QLatin1String("api-compat-level"), 2}
+ {QLatin1String("api-level"), 5},
+ {QLatin1String("api-compat-level"), 2},
+ {QLatin1String("lsp-socket"), lspSocket}
};
}
diff --git a/src/app/qbs/sessionpacket.h b/src/app/qbs/sessionpacket.h
index d919ff340..e77b30b75 100644
--- a/src/app/qbs/sessionpacket.h
+++ b/src/app/qbs/sessionpacket.h
@@ -55,7 +55,7 @@ public:
QJsonObject retrievePacket();
static QByteArray createPacket(const QJsonObject &packet);
- static QJsonObject helloMessage();
+ static QJsonObject helloMessage(const QString &lspSocket);
private:
bool isComplete() const;
diff --git a/src/app/qbs/sessionpacketreader.cpp b/src/app/qbs/sessionpacketreader.cpp
index e99ea01ed..daba30d7e 100644
--- a/src/app/qbs/sessionpacketreader.cpp
+++ b/src/app/qbs/sessionpacketreader.cpp
@@ -42,6 +42,8 @@
#include "sessionpacket.h"
#include "stdinreader.h"
+#include <QPointer>
+
namespace qbs {
namespace Internal {
@@ -64,8 +66,13 @@ void SessionPacketReader::start()
StdinReader * const stdinReader = StdinReader::create(this);
connect(stdinReader, &StdinReader::errorOccurred, this, &SessionPacketReader::errorOccurred);
connect(stdinReader, &StdinReader::dataAvailable, this, [this](const QByteArray &data) {
+ /* Because this SessionPacketReader can be destroyed in the emit packetReceived,
+ * use a `QPointer self(this)` to check whether this instance still exists.
+ * When self evaluates to false, this instance should no longer be referenced,
+ * so the parent QObject and d should no longer be used in any way. */
+ QPointer self(this);
d->incomingData += data;
- while (!d->incomingData.isEmpty()) {
+ while (self && !d->incomingData.isEmpty()) {
switch (d->currentPacket.parseInput(d->incomingData)) {
case SessionPacket::Status::Invalid:
emit errorOccurred(tr("Received invalid input."));
diff --git a/src/app/qbs/stdinreader.cpp b/src/app/qbs/stdinreader.cpp
index 5f00d7de4..4708ff53c 100644
--- a/src/app/qbs/stdinreader.cpp
+++ b/src/app/qbs/stdinreader.cpp
@@ -43,6 +43,7 @@
#include <QtCore/qfile.h>
#include <QtCore/qsocketnotifier.h>
+#include <QtCore/qthread.h>
#include <QtCore/qtimer.h>
#include <cerrno>
@@ -111,46 +112,183 @@ public:
WindowsStdinReader(QObject *parent) : StdinReader(parent) {}
private:
- void start() override
- {
#ifdef Q_OS_WIN32
- m_stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
- if (!m_stdinHandle) {
- emit errorOccurred(tr("Failed to create handle for standard input."));
- return;
+ class FileReaderThread : public QThread
+ {
+ public:
+ FileReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ ~FileReaderThread()
+ {
+ wait();
+ CloseHandle(m_exitEvent);
+ }
+
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
+ char buf[1024];
+ while (true) {
+ DWORD bytesRead = 0;
+ if (!ReadFile(m_stdIn, buf, sizeof buf, &bytesRead, nullptr)) {
+ emit r->errorOccurred(tr("Failed to read from input channel."));
+ break;
+ }
+ if (!bytesRead)
+ break;
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
+ }
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
+
+ class ConsoleReaderThread : public QThread
+ {
+ public:
+ ConsoleReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ virtual ~ConsoleReaderThread() override
+ {
+ SetEvent(m_exitEvent);
+ wait();
+ CloseHandle(m_exitEvent);
}
- // A timer seems slightly less awful than to block in a thread
- // (how would we abort that one?), but ideally we'd like
- // to have a signal-based approach like in the Unix variant.
- const auto timer = new QTimer(this);
- connect(timer, &QTimer::timeout, this, [this, timer] {
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
+ DWORD origConsoleMode;
+ GetConsoleMode(m_stdIn, &origConsoleMode);
+ DWORD consoleMode = ENABLE_PROCESSED_INPUT;
+ SetConsoleMode(m_stdIn, consoleMode);
+
+ HANDLE handles[2] = {m_exitEvent, m_stdIn};
char buf[1024];
- DWORD bytesAvail;
- if (!PeekNamedPipe(m_stdinHandle, nullptr, 0, nullptr, &bytesAvail, nullptr)) {
- timer->stop();
- emit errorOccurred(tr("Failed to read from input channel."));
+ while (true) {
+ auto result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ if (result == WAIT_OBJECT_0)
+ break;
+ INPUT_RECORD consoleInput;
+ DWORD inputsRead = 0;
+ if (!PeekConsoleInputA(m_stdIn, &consoleInput, 1, &inputsRead)) {
+ emit r->errorOccurred(tr("Failed to read from input channel."));
+ break;
+ }
+ if (inputsRead) {
+ if (consoleInput.EventType != KEY_EVENT
+ || !consoleInput.Event.KeyEvent.bKeyDown
+ || !consoleInput.Event.KeyEvent.uChar.AsciiChar) {
+ if (!ReadConsoleInputA(m_stdIn, &consoleInput, 1, &inputsRead)) {
+ emit r->errorOccurred(tr("Failed to read console input."));
+ break;
+ }
+ } else {
+ DWORD bytesRead = 0;
+ if (!ReadConsoleA(m_stdIn, buf, sizeof buf, &bytesRead, nullptr)) {
+ emit r->errorOccurred(tr("Failed to read console."));
+ break;
+ }
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
+ }
+ }
+ }
+ SetConsoleMode(m_stdIn, origConsoleMode);
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
+
+ class PipeReaderThread : public QThread
+ {
+ public:
+ PipeReaderThread(WindowsStdinReader &parent, HANDLE stdInHandle, HANDLE exitEventHandle)
+ : QThread(&parent), m_stdIn{stdInHandle}, m_exitEvent{exitEventHandle} { }
+ virtual ~PipeReaderThread() override
+ {
+ SetEvent(m_exitEvent);
+ wait();
+ CloseHandle(m_exitEvent);
+ }
+
+ void run() override
+ {
+ WindowsStdinReader *r = static_cast<WindowsStdinReader *>(parent());
+
+ OVERLAPPED overlapped = {};
+ overlapped.hEvent = CreateEventA(NULL, TRUE, TRUE, NULL);
+ if (!overlapped.hEvent) {
+ emit r->errorOccurred(StdinReader::tr("Failed to create handle for overlapped event."));
return;
}
- while (bytesAvail > 0) {
- DWORD bytesRead;
- if (!ReadFile(m_stdinHandle, buf, std::min<DWORD>(bytesAvail, sizeof buf),
- &bytesRead, nullptr)) {
- timer->stop();
- emit errorOccurred(tr("Failed to read from input channel."));
- return;
+
+ char buf[1024];
+ DWORD bytesRead;
+ HANDLE handles[2] = {m_exitEvent, overlapped.hEvent};
+ while (true) {
+ bytesRead = 0;
+ auto readResult = ReadFile(m_stdIn, buf, sizeof buf, NULL, &overlapped);
+ if (!readResult) {
+ if (GetLastError() != ERROR_IO_PENDING) {
+ emit r->errorOccurred(StdinReader::tr("ReadFile Failed."));
+ break;
+ }
+
+ auto result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ if (result == WAIT_OBJECT_0)
+ break;
+ }
+ if (!GetOverlappedResult(m_stdIn, &overlapped, &bytesRead, FALSE)) {
+ if (GetLastError() != ERROR_HANDLE_EOF)
+ emit r->errorOccurred(StdinReader::tr("Error GetOverlappedResult."));
+ break;
}
- emit dataAvailable(QByteArray(buf, bytesRead));
- bytesAvail -= bytesRead;
+ emit r->dataAvailable(QByteArray(buf, bytesRead));
}
- });
- timer->start(10);
+ CancelIo(m_stdIn);
+ CloseHandle(overlapped.hEvent);
+ }
+ private:
+ HANDLE m_stdIn;
+ HANDLE m_exitEvent;
+ };
#endif
- }
+ void start() override
+ {
#ifdef Q_OS_WIN32
- HANDLE m_stdinHandle;
+ HANDLE stdInHandle = GetStdHandle(STD_INPUT_HANDLE);
+ if (!stdInHandle) {
+ emit errorOccurred(StdinReader::tr("Failed to create handle for standard input."));
+ return;
+ }
+ HANDLE exitEventHandle = CreateEventA(NULL, TRUE, FALSE, NULL);
+ if (!exitEventHandle) {
+ emit errorOccurred(StdinReader::tr("Failed to create handle for exit event."));
+ return;
+ }
+
+ auto result = GetFileType(stdInHandle);
+ switch (result) {
+ case FILE_TYPE_CHAR:
+ (new ConsoleReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ case FILE_TYPE_PIPE:
+ (new PipeReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ case FILE_TYPE_DISK:
+ (new FileReaderThread(*this, stdInHandle, exitEventHandle))->start();
+ return;
+ default:
+ emit errorOccurred(StdinReader::tr("Unable to handle unknown input type"));
+ return;
+ }
#endif
+ }
};
StdinReader *StdinReader::create(QObject *parent)
diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt
index 4bf7575c3..97b5e6fad 100644
--- a/src/lib/corelib/CMakeLists.txt
+++ b/src/lib/corelib/CMakeLists.txt
@@ -266,14 +266,14 @@ set(LOADER_SOURCES
probesresolver.h
productitemmultiplexer.cpp
productitemmultiplexer.h
+ productresolver.cpp
+ productresolver.h
productscollector.cpp
productscollector.h
- productshandler.cpp
- productshandler.h
+ productsresolver.cpp
+ productsresolver.h
projectresolver.cpp
projectresolver.h
- projecttreebuilder.cpp
- projecttreebuilder.h
)
list_transform_prepend(LOADER_SOURCES loader/)
@@ -412,6 +412,7 @@ set(TOOLS_HEADERS
generateoptions.h
installoptions.h
joblimits.h
+ mutexdata.h
preferences.h
processresult.h
profile.h
diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp
index 5e810f0b6..35766efa4 100644
--- a/src/lib/corelib/api/internaljobs.cpp
+++ b/src/lib/corelib/api/internaljobs.cpp
@@ -77,8 +77,8 @@ public:
{
std::lock_guard<std::mutex> lock(m_cancelMutex);
m_canceled = true;
- if (scriptEngine())
- scriptEngine()->cancel();
+ for (ScriptEngine * const engine : scriptEngines())
+ engine->cancel();
}
private:
@@ -324,9 +324,9 @@ void InternalSetupProjectJob::execute()
void InternalSetupProjectJob::resolveProjectFromScratch(ScriptEngine *engine)
{
- ProjectResolver resolver(engine, logger());
+ ProjectResolver resolver(m_parameters, engine, logger());
resolver.setProgressObserver(observer());
- m_newProject = resolver.resolve(m_parameters);
+ m_newProject = resolver.resolve();
QBS_CHECK(m_newProject);
}
diff --git a/src/lib/corelib/api/jobs.cpp b/src/lib/corelib/api/jobs.cpp
index 7a845b0ac..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,
diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp
index 8d5152a24..53c711b49 100644
--- a/src/lib/corelib/api/project.cpp
+++ b/src/lib/corelib/api/project.cpp
@@ -269,14 +269,10 @@ GroupData ProjectPrivate::createGroupDataFromGroup(const GroupPtr &resolvedGroup
for (const auto &sa : resolvedGroup->files) {
ArtifactData artifact = createApiSourceArtifact(sa);
setupInstallData(artifact, product);
- group.d->sourceArtifacts.push_back(artifact);
- }
- if (resolvedGroup->wildcards) {
- for (const auto &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());
@@ -801,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};
}
/*!
@@ -960,6 +962,12 @@ std::set<QString> Project::buildSystemFiles() const
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,
const QString &inputFilePath, const QString &outputFileTag, ErrorInfo *error) const
{
diff --git a/src/lib/corelib/api/project.h b/src/lib/corelib/api/project.h
index 7ea7645a7..7490eb41e 100644
--- a/src/lib/corelib/api/project.h
+++ b/src/lib/corelib/api/project.h
@@ -136,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;
diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp
index 9de2d00ee..628fe2a5d 100644
--- a/src/lib/corelib/api/projectdata.cpp
+++ b/src/lib/corelib/api/projectdata.cpp
@@ -40,16 +40,16 @@
#include "projectdata_p.h"
#include "propertymap_p.h"
+#include <language/builtindeclarations.h>
#include <language/language.h>
#include <language/propertymapinternal.h>
-#include <loader/productitemmultiplexer.h>
+#include <loader/loaderutils.h>
#include <tools/fileinfo.h>
#include <tools/jsliterals.h>
#include <tools/qbsassert.h>
#include <tools/qttools.h>
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
-#include <tools/stringconstants.h>
#include <QtCore/qdir.h>
#include <QtCore/qjsonarray.h>
@@ -75,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));
}
@@ -559,7 +559,7 @@ const QString &ProductData::name() const
*/
QString ProductData::fullDisplayName() const
{
- return ProductItemMultiplexer::fullProductDisplayName(name(), multiplexConfigurationId());
+ return fullProductDisplayName(name(), multiplexConfigurationId());
}
/*!
@@ -732,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)
@@ -954,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
diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h
index 9fe6445c7..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,
diff --git a/src/lib/corelib/api/projectfileupdater.cpp b/src/lib/corelib/api/projectfileupdater.cpp
index 5b9f9bced..604600f8e 100644
--- a/src/lib/corelib/api/projectfileupdater.cpp
+++ b/src/lib/corelib/api/projectfileupdater.cpp
@@ -210,11 +210,7 @@ ProjectFileGroupInserter::ProjectFileGroupInserter(ProductData product, QString
static int extractLine(const QString &fileContent, int pos)
{
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
- return fileContent.leftRef(pos).count(QLatin1Char('\n'));
-#else
return QStringView{fileContent}.left(pos).count(QLatin1Char('\n'));
-#endif
}
void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast)
diff --git a/src/lib/corelib/api/runenvironment.cpp b/src/lib/corelib/api/runenvironment.cpp
index 2544f549e..23d0359b0 100644
--- a/src/lib/corelib/api/runenvironment.cpp
+++ b/src/lib/corelib/api/runenvironment.cpp
@@ -225,7 +225,7 @@ int RunEnvironment::doRunShell()
static QString findExecutable(const QStringList &fileNames)
{
const QStringList path = QString::fromLocal8Bit(qgetenv("PATH"))
- .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
+ .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
for (const QString &fileName : fileNames) {
const QString exeFileName = HostOsInfo::appendExecutableSuffix(fileName);
@@ -363,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")
diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp
index d641627e7..10aae5991 100644
--- a/src/lib/corelib/buildgraph/buildgraph.cpp
+++ b/src/lib/corelib/buildgraph/buildgraph.cpp
@@ -710,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);
}
}
@@ -766,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);
}
}
diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp
index fc05d292c..ffae52ab2 100644
--- a/src/lib/corelib/buildgraph/buildgraphloader.cpp
+++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp
@@ -307,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;
@@ -334,11 +336,20 @@ void BuildGraphLoader::trackProjectChanges()
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);
- ProjectResolver resolver(m_evalContext->engine(), m_logger);
+ ProjectResolver resolver(m_parameters, m_evalContext->engine(), m_logger);
resolver.setProgressObserver(m_evalContext->observer());
resolver.setOldProjectProbes(restoredProject->probes);
if (!m_parameters.forceProbeExecution())
@@ -350,7 +361,7 @@ void BuildGraphLoader::trackProjectChanges()
resolver.setOldProductProbes(restoredProbes);
if (!m_parameters.overrideBuildGraphData())
resolver.setStoredProfiles(restoredProject->profileConfigs);
- m_result.newlyResolvedProject = resolver.resolve(m_parameters);
+ m_result.newlyResolvedProject = resolver.resolve();
std::vector<ResolvedProductPtr> allNewlyResolvedProducts
= m_result.newlyResolvedProject->allProducts();
@@ -497,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;
@@ -510,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;
}
}
@@ -524,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;
}
}
@@ -539,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;
}
}
@@ -555,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;
}
}
@@ -574,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 : 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;
}
@@ -598,25 +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 = Internal::any_of(group->wildcards->dirTimeStamps,
- [](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 auto &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;
+ }
}
}
}
@@ -630,8 +632,7 @@ 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) {
@@ -642,7 +643,7 @@ bool BuildGraphLoader::hasBuildSystemFileChanged(const Set<QString> &buildSystem
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;
}
}
@@ -769,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;
}
}
@@ -858,11 +860,14 @@ 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) {
@@ -880,7 +885,7 @@ bool BuildGraphLoader::checkConfigCompatibility()
"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 "
@@ -895,7 +900,8 @@ 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());
const QVariantMap profileConfigsTree = restoredProject->fullProfileConfigsTree();
diff --git a/src/lib/corelib/buildgraph/buildgraphloader.h b/src/lib/corelib/buildgraph/buildgraphloader.h
index c62ba7fa7..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>
@@ -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/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp
index 869d7552a..2b8232a0a 100644
--- a/src/lib/corelib/buildgraph/executor.cpp
+++ b/src/lib/corelib/buildgraph/executor.cpp
@@ -263,6 +263,7 @@ void Executor::doBuild()
QBS_CHECK(!m_project->buildData->evaluationContext);
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());
diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
index 6d0c5cdbd..0c73f599f 100644
--- a/src/lib/corelib/buildgraph/inputartifactscanner.cpp
+++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
@@ -103,9 +103,14 @@ 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) {
@@ -250,14 +255,9 @@ void InputArtifactScanner::scanForScannerFileDependencies(DependencyScanner *sca
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);
diff --git a/src/lib/corelib/buildgraph/nodetreedumper.cpp b/src/lib/corelib/buildgraph/nodetreedumper.cpp
index 8475a46cf..6ad597c70 100644
--- a/src/lib/corelib/buildgraph/nodetreedumper.cpp
+++ b/src/lib/corelib/buildgraph/nodetreedumper.cpp
@@ -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/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
index b308e9c47..52a8fe75d 100644
--- a/src/lib/corelib/buildgraph/processcommandexecutor.cpp
+++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
@@ -291,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'), QBS_SKIP_EMPTY_PARTS);
+ *target = contentString.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
}
}
diff --git a/src/lib/corelib/buildgraph/rulecommands.cpp b/src/lib/corelib/buildgraph/rulecommands.cpp
index 6acd1d68c..73d05eaca 100644
--- a/src/lib/corelib/buildgraph/rulecommands.cpp
+++ b/src/lib/corelib/buildgraph/rulecommands.cpp
@@ -99,15 +99,11 @@ 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;
+ 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,
diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp
index 5cc4be96e..e07abd74a 100644
--- a/src/lib/corelib/buildgraph/rulesapplicator.cpp
+++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp
@@ -293,8 +293,9 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, JSValue prepare
}
outputArtifact->properties->setValue(artifactModulesCfg);
if (!outputInfo.newlyCreated
- && (outputArtifact->fileTags() != outputInfo.oldFileTags
- || outputArtifact->properties->value() != outputInfo.oldProperties)) {
+ && (outputArtifact->fileTags() != outputInfo.oldFileTags
+ || !qVariantMapsEqual(
+ outputArtifact->properties->value(), outputInfo.oldProperties))) {
invalidateArtifactAsRuleInputIfNecessary(outputArtifact);
}
}
@@ -664,8 +665,10 @@ Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const JSValue &ob
connect(outputInfo.artifact, dependency);
}
ArtifactBindingsExtractor().apply(engine(), outputInfo.artifact, obj);
- if (!outputInfo.newlyCreated && (outputInfo.artifact->fileTags() != outputInfo.oldFileTags
- || outputInfo.artifact->properties->value() != outputInfo.oldProperties)) {
+ if (!outputInfo.newlyCreated
+ && (outputInfo.artifact->fileTags() != outputInfo.oldFileTags
+ || !qVariantMapsEqual(
+ outputInfo.artifact->properties->value(), outputInfo.oldProperties))) {
invalidateArtifactAsRuleInputIfNecessary(outputInfo.artifact);
}
return outputInfo.artifact;
diff --git a/src/lib/corelib/buildgraph/transformerchangetracking.cpp b/src/lib/corelib/buildgraph/transformerchangetracking.cpp
index f0b8986f4..ae43e8219 100644
--- a/src/lib/corelib/buildgraph/transformerchangetracking.cpp
+++ b/src/lib/corelib/buildgraph/transformerchangetracking.cpp
@@ -158,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"
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 5e837168e..1a7890166 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -355,14 +355,14 @@ QbsLibrary {
"probesresolver.h",
"productitemmultiplexer.cpp",
"productitemmultiplexer.h",
+ "productresolver.cpp",
+ "productresolver.h",
"productscollector.cpp",
"productscollector.h",
- "productshandler.cpp",
- "productshandler.h",
+ "productsresolver.cpp",
+ "productsresolver.h",
"projectresolver.cpp",
"projectresolver.h",
- "projecttreebuilder.cpp",
- "projecttreebuilder.h",
]
}
Group {
@@ -514,6 +514,7 @@ QbsLibrary {
"generateoptions.h",
"installoptions.h",
"joblimits.h",
+ "mutexdata.h",
"preferences.h",
"processresult.h",
"profile.h",
diff --git a/src/lib/corelib/jsextensions/jsextension.h b/src/lib/corelib/jsextensions/jsextension.h
index e34c8ff83..d34fb2b83 100644
--- a/src/lib/corelib/jsextensions/jsextension.h
+++ b/src/lib/corelib/jsextensions/jsextension.h
@@ -176,8 +176,10 @@ public:
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_NewInt32(ctx, data.at(i)));
+ 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)
@@ -277,9 +279,9 @@ template<> struct FromArgHelper<QByteArray> {
const JSValue jsNumber = JS_GetPropertyUint32(ctx, v, i);
if (JS_VALUE_GET_TAG(jsNumber) != JS_TAG_INT)
throwError();
- int32_t n;
- JS_ToInt32(ctx, &n, jsNumber);
- if (n < 0 || n > 0xff)
+ uint32_t n;
+ JS_ToUint32(ctx, &n, jsNumber);
+ if (n > 0xff)
throwError();
data[i] = n;
}
diff --git a/src/lib/corelib/jsextensions/pkgconfigjs.cpp b/src/lib/corelib/jsextensions/pkgconfigjs.cpp
index 2d80ec770..817a3dfb9 100644
--- a/src/lib/corelib/jsextensions/pkgconfigjs.cpp
+++ b/src/lib/corelib/jsextensions/pkgconfigjs.cpp
@@ -121,7 +121,6 @@ QVariantMap packageToMap(const PcPackage &package)
case Type::AlwaysMatch:
break;
}
- result[QStringLiteral("comparison")] = QVariant::fromValue(qint32(version.comparison));
return result;
};
@@ -242,7 +241,7 @@ PkgConfig::Options PkgConfigJs::convertOptions(const QProcessEnvironment &env, c
[](const QString &str){ return str.toStdString(); });
result.disableUninstalled = map.value(QStringLiteral("disableUninstalled"), true).toBool();
result.staticMode = map.value(QStringLiteral("staticMode"), false).toBool();
- result.mergeDependencies = map.value(QStringLiteral("mergeDependencies"), true).toBool();
+ result.definePrefix = map.value(QStringLiteral("definePrefix"), false).toBool();
result.globalVariables =
variablesFromQVariantMap(map.value(QStringLiteral("globalVariables")).toMap());
result.systemVariables = envToVariablesMap(env);
diff --git a/src/lib/corelib/jsextensions/utilitiesextension.cpp b/src/lib/corelib/jsextensions/utilitiesextension.cpp
index e733d618b..cdcee59fa 100644
--- a/src/lib/corelib/jsextensions/utilitiesextension.cpp
+++ b/src/lib/corelib/jsextensions/utilitiesextension.cpp
@@ -782,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 {};
@@ -832,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) {
@@ -845,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);
@@ -875,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:
diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp
index acf50b4f3..44dc8a326 100644
--- a/src/lib/corelib/language/builtindeclarations.cpp
+++ b/src/lib/corelib/language/builtindeclarations.cpp
@@ -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,23 @@ 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::Probe});
+ 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.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;
diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp
index 9a19828bb..084f2f4a9 100644
--- a/src/lib/corelib/language/evaluator.cpp
+++ b/src/lib/corelib/language/evaluator.cpp
@@ -77,13 +77,15 @@ static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t
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(scriptEngine->registerClass("Evaluator", nullptr, nullptr, JS_UNDEFINED,
- getEvalPropertyNames, getEvalProperty))
+ getEvalPropertyNames, getEvalPropertySafe))
{
scriptEngine->registerEvaluator(this);
}
@@ -96,6 +98,7 @@ Evaluator::~Evaluator()
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)) {
@@ -191,6 +194,15 @@ std::optional<QStringList> Evaluator::optionalStringListValue(
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
{
const ValueConstPtr v = item->property(name);
@@ -215,24 +227,13 @@ JSValue 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 = JS_NewObjectClass(m_scriptEngine->context(), m_scriptClass);
attachPointerTo(scriptValue, edata);
return scriptValue;
}
-void Evaluator::clearCache(const Item *item)
-{
- const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item),
- m_scriptEngine->dataWithPtrClass());
- if (data) {
- for (const auto value : std::as_const(data->valueCache))
- JS_FreeValue(m_scriptEngine->context(), value);
- data->valueCache.clear();
- }
-}
-
void Evaluator::handleEvaluationError(const Item *item, const QString &name)
{
throwOnEvaluationError(m_scriptEngine, [&item, &name] () {
@@ -278,6 +279,42 @@ Evaluator::FileContextScopes Evaluator::fileContextScopes(const FileContextConst
return result;
}
+// 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)
+{
+ 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);
+ }
+}
+
+void Evaluator::clearCacheIfInvalidated(EvaluationData &edata)
+{
+ 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::clearCache(EvaluationData &edata)
+{
+ for (const auto value : std::as_const(edata.valueCache))
+ JS_FreeValue(m_scriptEngine->context(), value);
+ edata.valueCache.clear();
+}
+
void throwOnEvaluationError(ScriptEngine *engine,
const std::function<CodeLocation()> &provideFallbackCodeLocation)
{
@@ -773,7 +810,7 @@ private:
void handle(VariantValue *variantValue) override
{
- *result = engine->toScriptValue(variantValue->value());
+ *result = engine->toScriptValue(variantValue->value(), variantValue->id());
engine->takeOwnership(*result);
}
};
@@ -853,10 +890,11 @@ static void collectValuesFromNextChain(
struct EvalResult { JSValue v = JS_UNDEFINED; bool found = false; };
static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item *item,
- const QString &name, const EvaluationData *data)
+ const QString &name, EvaluationData *data)
{
Evaluator * const evaluator = data->evaluator;
- const bool isModuleInstance = item->type() == ItemType::ModuleInstance;
+ 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)) {
@@ -871,6 +909,7 @@ static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item
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)
@@ -893,6 +932,7 @@ static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item
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());
@@ -957,5 +997,15 @@ static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSValue o
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;
+ }
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h
index d791a4c5d..d86e08eb1 100644
--- a/src/lib/corelib/language/evaluator.h
+++ b/src/lib/corelib/language/evaluator.h
@@ -49,11 +49,13 @@
#include <QtCore/qhash.h>
#include <functional>
+#include <mutex>
#include <optional>
#include <stack>
namespace qbs {
namespace Internal {
+class EvaluationData;
class FileTags;
class Logger;
class PropertyDeclaration;
@@ -83,6 +85,8 @@ public:
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,
JSValue &v);
@@ -99,6 +103,8 @@ public:
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() { return m_propertyDependencies; }
void clearPropertyDependencies() { m_propertyDependencies.clear(); }
@@ -113,9 +119,10 @@ public:
bool isNonDefaultValue(const Item *item, const QString &name) const;
private:
- void onItemPropertyChanged(Item *item) override { clearCache(item); }
+ void onItemPropertyChanged(Item *item) override { invalidateCache(item); }
bool evaluateProperty(JSValue *result, const Item *item, const QString &name,
bool *propertyWasSet);
+ void clearCache(EvaluationData &edata);
ScriptEngine * const m_scriptEngine;
const JSClassID m_scriptClass;
@@ -124,6 +131,8 @@ private:
QString m_pathPropertiesBaseDir;
PropertyDependencies m_propertyDependencies;
std::stack<QualifiedId> m_requestedProperties;
+ std::mutex m_cacheInvalidationMutex;
+ Set<const Item *> m_invalidatedCaches;
bool m_valueCacheEnabled = false;
};
@@ -139,7 +148,9 @@ public:
m_evaluator->setPathPropertiesBaseDir(baseDir);
}
- ~EvalCacheEnabler()
+ ~EvalCacheEnabler() { reset(); }
+
+ void reset()
{
m_evaluator->setCachingEnabled(false);
m_evaluator->clearPathPropertiesBaseDir();
diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp
index 647d05aa2..e5de8f195 100644
--- a/src/lib/corelib/language/item.cpp
+++ b/src/lib/corelib/language/item.cpp
@@ -40,13 +40,13 @@
#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>
@@ -60,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;
@@ -91,14 +83,14 @@ Item *Item::clone() const
dup->m_children.reserve(m_children.size());
for (const Item * const child : std::as_const(m_children)) {
- Item *clonedChild = child->clone();
+ 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;
@@ -128,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))
@@ -139,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);
@@ -156,29 +152,30 @@ 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();
- ItemValuePtr result = ItemValue::create(Item::create(m_pool, itemTemplate->type()),
+ ItemValuePtr result = ItemValue::create(Item::create(&pool, itemTemplate->type()),
createdByPropertiesBlock);
setProperty(name, result);
return result;
@@ -188,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);
}
@@ -196,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);
}
@@ -211,6 +208,28 @@ 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
{
auto it = m_propertyDeclarations.find(name);
@@ -230,12 +249,12 @@ void Item::addModule(const Item::Module &module)
QBS_CHECK(none_of(m_modules, [&](const Module &m) {
if (m.name != module.name)
return false;
- if (!!module.productInfo != !!m.productInfo)
+ if (!!module.product != !!m.product)
return true;
- if (!module.productInfo)
+ if (!module.product)
return true;
- if (module.productInfo->multiplexId == m.productInfo->multiplexId
- && module.productInfo->profile == m.productInfo->profile) {
+ if (module.product->multiplexConfigurationId == m.product->multiplexConfigurationId
+ && module.product->profileName == m.product->profileName) {
return true;
}
return false;
@@ -245,17 +264,13 @@ void Item::addModule(const Item::Module &module)
m_modules.push_back(module);
}
-void Item::setObserver(ItemObserver *observer) const
-{
- QBS_ASSERT(!observer || !m_observer, return); // warn if accidentally overwritten
- m_observer = observer;
-}
-
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
@@ -270,8 +285,14 @@ bool Item::isPresentModule() const
return v && v->type() == Value::JSSourceValueType;
}
+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) {
@@ -353,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);
}
@@ -457,5 +509,10 @@ void setScopeForDescendants(Item *item, Item *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 337cad78a..d0dde98c4 100644
--- a/src/lib/corelib/language/item.h
+++ b/src/lib/corelib/language/item.h
@@ -53,7 +53,9 @@
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
-#include <optional>
+#include <atomic>
+#include <mutex>
+#include <utility>
#include <vector>
namespace qbs {
@@ -64,32 +66,45 @@ 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
{
QualifiedId name;
Item *item = nullptr;
- struct ProductInfo {
- ProductInfo(Item *i, const QString &m, const QString &p)
- : item(i), multiplexId(m), profile(p) {}
- Item *item = nullptr;
- QString multiplexId;
- QString profile;
- };
- std::optional<ProductInfo> productInfo; // Set if and only if the dep is a product.
+ ProductContext *product = nullptr; // Set if and only if the dep is a product.
- // All items that declared an explicit dependency on this module. Can contain any
+ // All the sites that declared an explicit dependency on this module. Can contain any
// number of module instances and at most one product.
- std::vector<Item *> loadingItems;
+ using ParametersWithPriority = std::pair<QVariantMap, int>;
+ struct LoadContext {
+ LoadContext(Item *dependsItem,
+ const ParametersWithPriority &parameters)
+ : dependsItem(dependsItem), parameters(parameters) {}
+ LoadContext(Item *dependsItem, ParametersWithPriority &&parameters)
+ : 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;
@@ -105,11 +120,11 @@ public:
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; }
@@ -119,8 +134,8 @@ public:
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; }
- PropertyMap &properties() { 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;
@@ -141,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; }
@@ -165,6 +182,7 @@ public:
static void removeChild(Item *parent, Item *child);
void dump() const;
bool isPresentModule() const;
+ bool isFallbackModule() const;
void setupForBuiltinType(DeprecationWarningMode deprecationMode, Logger &logger);
void copyProperty(const QString &propertyName, Item *target) const;
void overrideProperties(
@@ -180,18 +198,23 @@ public:
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;
@@ -199,6 +222,10 @@ 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; }
@@ -207,6 +234,22 @@ Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString
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/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/language.cpp b/src/lib/corelib/language/language.cpp
index 33fd3c6a4..b3f4b2a64 100644
--- a/src/lib/corelib/language/language.cpp
+++ b/src/lib/corelib/language/language.cpp
@@ -51,7 +51,8 @@
#include <buildgraph/rulegraph.h> // TODO: Move to language?
#include <buildgraph/transformer.h>
#include <jsextensions/jsextensions.h>
-#include <loader/productitemmultiplexer.h>
+#include <language/value.h>
+#include <loader/loaderutils.h>
#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
@@ -117,6 +118,12 @@ bool Probe::needsReconfigure(const FileTime &referenceTime) const
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
@@ -137,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.
@@ -325,7 +307,7 @@ std::vector<SourceArtifactPtr> ResolvedProduct::allFiles() const
{
std::vector<SourceArtifactPtr> lst;
for (const auto &group : groups)
- lst << group->allFiles();
+ lst << group->files;
return lst;
}
@@ -338,7 +320,7 @@ std::vector<SourceArtifactPtr> ResolvedProduct::allEnabledFiles() const
std::vector<SourceArtifactPtr> lst;
for (const auto &group : groups) {
if (group->enabled)
- lst << group->allFiles();
+ lst << group->files;
}
return lst;
}
@@ -374,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)
@@ -427,7 +411,7 @@ QString ResolvedProduct::uniqueName() const
QString ResolvedProduct::fullDisplayName() const
{
- return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId);
+ return fullProductDisplayName(name, multiplexConfigurationId);
}
QString ResolvedProduct::profile() const
@@ -503,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)
{
@@ -710,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('/'), QBS_SKIP_EMPTY_PARTS);
+ QStringList parts = pattern.split(QLatin1Char('/'), Qt::SkipEmptyParts);
if (FileInfo::isAbsolute(pattern)) {
QString rootDir;
if (HostOsInfo::isWindowsHost() && pattern.at(0) != QLatin1Char('/')) {
@@ -738,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
@@ -757,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();
@@ -785,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;
@@ -798,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)
{
@@ -931,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)
@@ -958,20 +965,19 @@ 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
diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h
index 1dae572a1..774d703d0 100644
--- a/src/lib/corelib/language/language.h
+++ b/src/lib/corelib/language/language.h
@@ -49,6 +49,7 @@
#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>
@@ -116,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;
@@ -134,6 +138,8 @@ 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:
@@ -144,21 +150,27 @@ private:
QString configureScript,
QVariantMap properties,
QVariantMap initialProperties,
+ QMap<QString, VariantValuePtr> values,
std::vector<QString> importedFilesUsed)
: m_globalId(std::move(globalId))
, m_location(location)
, 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;
};
@@ -230,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:
@@ -248,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;
- const ResolvedGroup *group = nullptr; // The owning group.
+ // to be restored by the owning class
+ QString prefix;
+ QString baseDir;
+ QString buildDir;
+ Set<QString> expandedFiles;
+
+ // 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
@@ -287,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);
@@ -692,6 +703,7 @@ public:
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.
@@ -723,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;
diff --git a/src/lib/corelib/language/moduleproviderinfo.h b/src/lib/corelib/language/moduleproviderinfo.h
index 500d370cc..c35ed220a 100644
--- a/src/lib/corelib/language/moduleproviderinfo.h
+++ b/src/lib/corelib/language/moduleproviderinfo.h
@@ -83,24 +83,25 @@ public:
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:
- using CacheKey = std::tuple<
- QString /*name*/,
- QVariantMap /*config*/,
- QVariantMap /*qbsModule*/,
- int /*lookup*/
- >;
- using ModuleProvidersCache = QHash<CacheKey, ModuleProviderInfo>;
-
ModuleProvidersCache providers;
template<PersistentPool::OpType opType> void completeSerializationOp(PersistentPool &pool)
diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp
index 215918462..d56ab3bb0 100644
--- a/src/lib/corelib/language/propertydeclaration.cpp
+++ b/src/lib/corelib/language/propertydeclaration.cpp
@@ -46,6 +46,7 @@
#include "value.h"
#include <api/languageinfo.h>
+#include <loader/loaderutils.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/setupprojectparameters.h>
@@ -105,7 +106,6 @@ public:
DeprecationInfo deprecationInfo;
};
-
PropertyDeclaration::PropertyDeclaration()
: d(new PropertyDeclarationData)
{
@@ -308,17 +308,78 @@ QVariant PropertyDeclaration::convertToPropertyType(const QVariant &v, Type t,
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(const Set<Item *> &disabledItems,
- const SetupProjectParameters &params, Logger &logger)
- : m_disabledItems(disabledItems)
- , m_params(params)
- , m_logger(logger)
- { }
- void operator()(Item *item) { handleItem(item); }
+ PropertyDeclarationCheck(LoaderState &loaderState) : m_loaderState(loaderState) {}
+ void operator()(Item *item)
+ {
+ m_checkingProject = item->type() == ItemType::Project;
+ handleItem(item);
+ }
private:
void handle(JSSourceValue *value) override
@@ -326,7 +387,7 @@ private:
if (!value->createdByPropertiesBlock()) {
const ErrorInfo error(Tr::tr("Property '%1' is not declared.")
.arg(m_currentName), value->location());
- handlePropertyError(error, m_params, m_logger);
+ handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger());
}
}
void handle(ItemValue *value) override
@@ -365,7 +426,7 @@ private:
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);
+ handlePropertyError(error, m_loaderState.parameters(), m_loaderState.logger());
return false;
}
@@ -373,16 +434,19 @@ private:
}
void handleItem(Item *item)
{
+ if (m_checkingProject && item->type() == ItemType::Product)
+ return;
if (!m_handledItems.insert(item).second)
return;
- if (m_disabledItems.contains(item)
- || item->type() == ItemType::Module
+ 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) {
+ || item->type() == ItemType::PropertiesInSubProject
+
+ || m_loaderState.topLevelProject().isDisabledItem(item)) {
return;
}
@@ -395,9 +459,12 @@ private:
const PropertyDeclaration decl = item->propertyDeclaration(it.key());
if (decl.isValid()) {
const ErrorInfo deprecationError = decl.checkForDeprecation(
- m_params.deprecationWarningMode(), it.value()->location(), m_logger);
- if (deprecationError.hasError())
- handlePropertyError(deprecationError, m_params, m_logger);
+ 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();
@@ -429,20 +496,18 @@ private:
Item *parentItem() const { return m_parentItems.back(); }
- const Set<Item *> &m_disabledItems;
+ LoaderState &m_loaderState;
Set<Item *> m_handledItems;
std::vector<Item *> m_parentItems;
QualifiedId m_currentModuleName;
QString m_currentName;
- const SetupProjectParameters &m_params;
- Logger &m_logger;
+ bool m_checkingProject = false;
};
} // namespace
-void checkPropertyDeclarations(Item *topLevelItem, const Set<Item *> &disabledItems,
- const SetupProjectParameters &params, Logger &logger)
+void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState)
{
- PropertyDeclarationCheck(disabledItems, params, logger)(topLevelItem);
+ (PropertyDeclarationCheck(loaderState))(topLevelItem);
}
} // namespace Internal
diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h
index 8c87faedb..79a39ecbd 100644
--- a/src/lib/corelib/language/propertydeclaration.h
+++ b/src/lib/corelib/language/propertydeclaration.h
@@ -41,7 +41,6 @@
#define QBS_PROPERTYDECLARATION_H
#include <tools/deprecationwarningmode.h>
-#include <tools/set.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstring.h>
@@ -53,11 +52,11 @@ QT_END_NAMESPACE
namespace qbs {
class CodeLocation;
class ErrorInfo;
-class SetupProjectParameters;
namespace Internal {
class DeprecationInfo;
class PropertyDeclarationData;
class Item;
+class LoaderState;
class Logger;
class PropertyDeclaration
@@ -129,13 +128,20 @@ public:
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, const Set<Item *> &disabledItems,
- const SetupProjectParameters &params, Logger &logger);
+void checkPropertyDeclarations(Item *topLevelItem, LoaderState &loaderState);
} // namespace Internal
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/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp
index 11d41b5c2..9131db7f5 100644
--- a/src/lib/corelib/language/scriptengine.cpp
+++ b/src/lib/corelib/language/scriptengine.cpp
@@ -58,6 +58,7 @@
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
+#include <QtCore/qdatetime.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdiriterator.h>
#include <QtCore/qfile.h>
@@ -196,6 +197,10 @@ void ScriptEngine::reset()
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());
@@ -481,6 +486,39 @@ void ScriptEngine::addInternalExtension(const char *name, JSValue ext)
m_internalExtensions.insert(QLatin1String(name), JS_DupValue(m_context, ext));
}
+JSValue ScriptEngine::asJsValue(const QVariant &v, quintptr id, bool frozen)
+{
+ 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().toString(Qt::ISODateWithMs).toUtf8().constData());
+ case QMetaType::QVariantMap:
+ return asJsValue(v.toMap(), id, frozen);
+ default:
+ return JS_UNDEFINED;
+ }
+}
+
JSValue ScriptEngine::asJsValue(const QByteArray &s)
{
return JS_NewArrayBufferCopy(
@@ -506,12 +544,21 @@ JSValue ScriptEngine::asJsValue(const QStringList &l)
return array;
}
-JSValue ScriptEngine::asJsValue(const QVariantMap &m)
+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(), makeJsVariant(m_context, it.value()));
- return obj;
+ 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);
}
void ScriptEngine::setPropertyOnGlobalObject(const QString &property, JSValue value)
@@ -520,13 +567,22 @@ void ScriptEngine::setPropertyOnGlobalObject(const QString &property, JSValue va
setJsProperty(m_context, globalObject, property, value);
}
-JSValue ScriptEngine::asJsValue(const QVariantList &l)
+JSValue ScriptEngine::asJsValue(const QVariantList &l, 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 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, makeJsVariant(m_context, l.at(i)));
- return array;
+ 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)
@@ -752,7 +808,7 @@ JSValue ScriptEngine::evaluate(JsValueOwner resultOwner, const QString &code,
m_scopeChains << scopeChain;
const QByteArray &codeStr = code.toUtf8();
- m_evalPositions.emplace(std::make_pair(filePath, line));
+ 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();
@@ -773,7 +829,7 @@ ScopedJsValueList ScriptEngine::argumentList(const QStringList &argumentNames,
JSValueList result;
for (const auto &name : argumentNames)
result.push_back(getJsProperty(m_context, context, name));
- return ScopedJsValueList(m_context, result);
+ return {m_context, result};
}
JSClassID ScriptEngine::registerClass(const char *name, JSClassCall *constructor,
@@ -1026,8 +1082,7 @@ ScriptEngine::Importer::Importer(
ScriptEngine::Importer::~Importer()
{
- if (m_engine.m_observeMode == ObserveMode::Enabled)
- m_engine.m_requireResults.clear();
+ m_engine.m_requireResults.clear();
m_engine.m_currentDirPathStack.pop();
m_engine.m_extensionSearchPathsStack.pop();
m_engine.uninstallImportFunctions();
diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h
index 4d797dd43..4a55392e3 100644
--- a/src/lib/corelib/language/scriptengine.h
+++ b/src/lib/corelib/language/scriptengine.h
@@ -58,6 +58,7 @@
#include <QtCore/qprocess.h>
#include <QtCore/qstring.h>
+#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
@@ -204,7 +205,7 @@ public:
JSValue newArray(int length, JsValueOwner owner);
void takeOwnership(JSValue v);
JSValue undefinedValue() const { return JS_UNDEFINED; }
- JSValue toScriptValue(const QVariant &v) const { return makeJsVariant(m_context, v); }
+ 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 = {});
@@ -283,11 +284,12 @@ public:
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);
- JSValue asJsValue(const QVariantMap &m);
+ 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); }
@@ -359,7 +361,7 @@ private:
std::unordered_map<QString, JSValue> m_jsFileCache;
bool m_propertyCacheEnabled = true;
bool m_active = false;
- bool m_canceling = false;
+ std::atomic_bool m_canceling = false;
QHash<PropertyCacheKey, QVariant> m_propertyCache;
PropertySet m_propertiesRequestedInScript;
QHash<QString, PropertySet> m_propertiesRequestedFromArtifact;
@@ -396,6 +398,7 @@ private:
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;
diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp
index fabc64ccd..634f54faf 100644
--- a/src/lib/corelib/language/value.cpp
+++ b/src/lib/corelib/language/value.cpp
@@ -54,11 +54,11 @@ Value::Value(Type t, bool createdByPropertiesBlock) : m_type(t)
m_flags |= OriginPropertiesBlock;
}
-Value::Value(const Value &other)
+Value::Value(const Value &other, ItemPool &pool)
: m_type(other.m_type),
m_scope(other.m_scope),
m_scopeName(other.m_scopeName),
- m_next(other.m_next ? other.m_next->clone() : ValuePtr()),
+ m_next(other.m_next ? other.m_next->clone(pool) : ValuePtr()),
m_candidates(other.m_candidates),
m_flags(other.m_flags)
{
@@ -134,18 +134,18 @@ 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_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 = transformed<std::vector<Alternative>>(
- other.m_alternatives, [](const auto &alternative) {
- return alternative.clone(); });
+ other.m_alternatives, [&pool](const auto &alternative) {
+ return alternative.clone(pool); });
}
JSSourceValuePtr JSSourceValue::create(bool createdByPropertiesBlock)
@@ -155,9 +155,9 @@ JSSourceValuePtr JSSourceValue::create(bool createdByPropertiesBlock)
JSSourceValue::~JSSourceValue() = default;
-ValuePtr JSSourceValue::clone() const
+ValuePtr JSSourceValue::clone(ItemPool &pool) const
{
- return std::make_shared<JSSourceValue>(*this);
+ return std::make_shared<JSSourceValue>(*this, pool);
}
QString JSSourceValue::sourceCodeForEvaluation() const
@@ -235,29 +235,51 @@ ItemValuePtr ItemValue::create(Item *item, bool 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();
+ return VariantValue::invalidValue();
if (static_cast<QMetaType::Type>(v.userType()) == QMetaType::Bool)
return v.toBool() ? VariantValue::trueValue() : VariantValue::falseValue();
- return std::make_shared<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 std::make_shared<VariantValue>(*this);
+ return std::make_shared<VariantValue>(*this, pool);
}
const VariantValuePtr &VariantValue::falseValue()
diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h
index 262813841..1a6746e24 100644
--- a/src/lib/corelib/language/value.h
+++ b/src/lib/corelib/language/value.h
@@ -50,6 +50,7 @@
namespace qbs {
namespace Internal {
class Item;
+class ItemPool;
class ValueHandler;
class Value
@@ -76,12 +77,13 @@ public:
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 *scope() const { return m_scope; }
@@ -144,13 +146,13 @@ class JSSourceValue : public Value
public:
explicit JSSourceValue(bool createdByPropertiesBlock);
- JSSourceValue(const JSSourceValue &other);
+ 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(QStringView sourceCode) { m_sourceCode = sourceCode; }
QStringView sourceCode() const { return m_sourceCode; }
@@ -180,10 +182,10 @@ public:
Alternative() = default;
Alternative(PropertyData c, PropertyData o, JSSourceValuePtr v)
: condition(std::move(c)), overrideListProperties(std::move(o)), value(std::move(v)) {}
- Alternative clone() const
+ 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;
@@ -222,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;
};
@@ -232,12 +234,15 @@ class VariantValue : public Value
{
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/loader/astimportshandler.cpp b/src/lib/corelib/loader/astimportshandler.cpp
index 90887e728..c0281ca24 100644
--- a/src/lib/corelib/loader/astimportshandler.cpp
+++ b/src/lib/corelib/loader/astimportshandler.cpp
@@ -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 : std::as_const(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/loader/astpropertiesitemhandler.cpp b/src/lib/corelib/loader/astpropertiesitemhandler.cpp
index fbb8761b5..8183e6b79 100644
--- a/src/lib/corelib/loader/astpropertiesitemhandler.cpp
+++ b/src/lib/corelib/loader/astpropertiesitemhandler.cpp
@@ -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);
}
@@ -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/loader/astpropertiesitemhandler.h b/src/lib/corelib/loader/astpropertiesitemhandler.h
index 413512ee5..804abb8a1 100644
--- a/src/lib/corelib/loader/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
index 06852afa9..059592e9c 100644
--- a/src/lib/corelib/loader/dependenciesresolver.cpp
+++ b/src/lib/corelib/loader/dependenciesresolver.cpp
@@ -43,8 +43,6 @@
#include "loaderutils.h"
#include "moduleinstantiator.h"
#include "moduleloader.h"
-#include "moduleproviderloader.h"
-#include "productitemmultiplexer.h"
#include <language/scriptengine.h>
#include <language/evaluator.h>
@@ -91,7 +89,6 @@ public:
VersionRange versionRange;
QVariantMap parameters;
bool limitToSubProject = false;
- FallbackMode fallbackMode = FallbackMode::Enabled;
bool requiredLocally = true;
bool requiredGlobally = true;
};
@@ -125,7 +122,6 @@ public:
VersionRange versionRange;
QVariantMap parameters;
bool limitToSubProject = false;
- FallbackMode fallbackMode = FallbackMode::Enabled;
bool requiredLocally = true;
bool requiredGlobally = true;
bool checkProduct = true;
@@ -141,24 +137,38 @@ public:
std::queue<FullyResolvedDependsItem> pendingResolvedDependencies;
bool requiredByLoadingItem = true;
};
-} // namespace
-static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2);
-static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties);
-static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v);
+class DependenciesContextImpl : public DependenciesContext
+{
+public:
+ DependenciesContextImpl(ProductContext &product, LoaderState &loaderState);
+
+ std::list<DependenciesResolvingState> stateStack;
+
+private:
+ std::pair<ProductDependency, ProductContext *> pendingDependency() const override;
-class DependenciesResolver::Private
+ void setSearchPathsForProduct(LoaderState &loaderState);
+
+ ProductContext &m_product;
+};
+
+class DependenciesResolver
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
+ DependenciesResolver(LoaderState &loaderState, ProductContext &product, Deferral deferral)
+ : m_loaderState(loaderState), m_product(product), m_deferral(deferral) {}
- void initializeState();
+ void resolve();
+ LoadModuleResult loadModule(Item *loadingItem, const FullyResolvedDependsItem &dependency);
+
+private:
void evaluateNextDependsItem();
HandleDependency handleResolvedDependencies();
- LoadModuleResult loadModule(Item *loadingItem, const FullyResolvedDependsItem &dependency);
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(
@@ -172,48 +182,63 @@ public:
std::optional<EvaluatedDependsItem> evaluateDependsItem(Item *item);
std::queue<FullyResolvedDependsItem> multiplexDependency(
const EvaluatedDependsItem &dependency);
- void setSearchPathsForProduct();
QVariantMap extractParameters(Item *dependsItem) const;
-
- LoaderState &loaderState;
- ModuleLoader moduleLoader{loaderState};
- std::unordered_map<ProductContext *, std::list<DependenciesResolvingState>> statePerProduct;
- qint64 elapsedTime = 0;
-
- ProductContext *product = nullptr;
- std::list<DependenciesResolvingState> *stateStack = nullptr;
- Deferral deferral = Deferral::Allowed;
+ 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;
};
-DependenciesResolver::DependenciesResolver(LoaderState &loaderState)
- : d(makePimpl<Private>(loaderState)) {}
-DependenciesResolver::~DependenciesResolver() = default;
+static bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2);
+static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v);
-bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral deferral)
+} // namespace
+
+void resolveDependencies(ProductContext &product, Deferral deferral, LoaderState &loaderState)
{
- QBS_CHECK(!product.dependenciesResolved);
+ DependenciesResolver(loaderState, product, deferral).resolve();
+}
- AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTime : nullptr);
+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;
+}
- d->product = &product;
- d->deferral = deferral;
- d->stateStack = &d->statePerProduct[&product];
+namespace {
- d->initializeState();
- SearchPathsManager searchPathsMgr(d->loaderState.itemReader(), product.searchPaths);
+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 (!d->stateStack->empty()) {
- auto &state = d->stateStack->front();
+ while (!stateStack().empty()) {
+ auto &state = stateStack().front();
// If we have pending FullyResolvedDependsItems, then these are handled first.
- if (d->handleResolvedDependencies() == HandleDependency::Defer)
- return false;
+ 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 != &d->stateStack->front())
+ if (&state != &stateStack().front())
continue;
// If we have a pending EvaluatedDependsItem, we multiplex it and then handle
@@ -223,17 +248,19 @@ bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral
// 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() && deferral == Deferral::Allowed)
- return false;
+ if (!state.currentDependsItem->productTypes.empty() && m_deferral == Deferral::Allowed)
+ return;
- state.pendingResolvedDependencies = d->multiplexDependency(*state.currentDependsItem);
+ 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.
- d->evaluateNextDependsItem();
+ evaluateNextDependsItem();
if (state.currentDependsItem)
continue;
@@ -244,9 +271,9 @@ bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral
// This ensures our invariant: A sorted module list in the product
// (dependers after dependencies).
- if (d->stateStack->size() > 1) {
+ if (stateStack().size() > 1) {
QBS_CHECK(state.loadingItem->type() == ItemType::ModuleInstance);
- Item::Modules &modules = product.item->modules();
+ 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;
@@ -256,79 +283,14 @@ bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral
modules.erase(loadingItemModule);
modules.push_back(tempModule);
}
- d->stateStack->pop_front();
- }
- return true;
-}
-
-void DependenciesResolver::checkDependencyParameterDeclarations(
- const Item *productItem, const QString &productName) const
-{
- d->moduleLoader.checkDependencyParameterDeclarations(productItem, productName);
-}
-
-void DependenciesResolver::setStoredModuleProviderInfo(
- const StoredModuleProviderInfo &moduleProviderInfo)
-{
- d->moduleLoader.setStoredModuleProviderInfo(moduleProviderInfo);
-}
-
-StoredModuleProviderInfo DependenciesResolver::storedModuleProviderInfo() const
-{
- return d->moduleLoader.storedModuleProviderInfo();
-}
-
-const Set<QString> &DependenciesResolver::tempQbsFiles() const
-{
- return d->moduleLoader.tempQbsFiles();
-}
-
-void DependenciesResolver::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- const QByteArray prefix(indent, ' ');
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("Setting up product dependencies took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
- d->moduleLoader.printProfilingInfo(indent + 2);
-}
-
-Item *DependenciesResolver::loadBaseModule(ProductContext &product, Item *item)
-{
- d->product = &product;
- d->stateStack = &d->statePerProduct[&product];
- d->deferral = Deferral::NotAllowed;
- const auto baseDependency = FullyResolvedDependsItem::makeBaseDependency();
- Item * const moduleItem = d->loadModule(item, baseDependency).moduleItem;
- if (Q_UNLIKELY(!moduleItem))
- throw ErrorInfo(Tr::tr("Cannot load base qbs module."));
- return moduleItem;
-}
-
-void DependenciesResolver::Private::initializeState()
-{
- if (!stateStack->empty())
- return;
-
- // Initialize the state with the direct Depends items of the product item.
- // This is executed once per product, while the function might be entered
- // multiple times due to deferrals.
- setSearchPathsForProduct();
- DependenciesResolvingState newState{product->item,};
- for (Item * const child : product->item->children()) {
- if (child->type() == ItemType::Depends)
- newState.pendingDependsItems.push(child);
+ stateStack().pop_front();
}
- stateStack->push_front(std::move(newState));
- stateStack->front().pendingResolvedDependencies.push(
- FullyResolvedDependsItem::makeBaseDependency());
+ m_product.dependenciesContext->dependenciesResolved = true;
}
-void DependenciesResolver::Private::evaluateNextDependsItem()
+void DependenciesResolver::evaluateNextDependsItem()
{
- auto &state = stateStack->front();
+ auto &state = stateStack().front();
while (!state.pendingDependsItems.empty()) {
QBS_CHECK(!state.currentDependsItem);
QBS_CHECK(state.pendingResolvedDependencies.empty());
@@ -344,9 +306,9 @@ void DependenciesResolver::Private::evaluateNextDependsItem()
}
}
-HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
+HandleDependency DependenciesResolver::handleResolvedDependencies()
{
- DependenciesResolvingState &state = stateStack->front();
+ DependenciesResolvingState &state = stateStack().front();
while (!state.pendingResolvedDependencies.empty()) {
QBS_CHECK(!state.currentDependsItem);
const FullyResolvedDependsItem dependency = state.pendingResolvedDependencies.front();
@@ -354,7 +316,7 @@ HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
const LoadModuleResult res = loadModule(state.loadingItem, dependency);
switch (res.handleDependency) {
case HandleDependency::Defer:
- QBS_CHECK(deferral == Deferral::Allowed);
+ QBS_CHECK(m_deferral == Deferral::Allowed);
// Optimization: We already looked up the product, so let's not do that again
// next time.
@@ -373,6 +335,7 @@ HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
state.pendingResolvedDependencies.pop();
continue;
}
+ m_deferral = Deferral::Allowed; // We made progress.
break;
}
@@ -385,42 +348,35 @@ HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
moduleDependsItems.push(child);
}
state.pendingResolvedDependencies.pop();
- stateStack->push_front(
+ stateStack().push_front(
{res.moduleItem, dependency, moduleDependsItems, {}, {},
dependency.requiredGlobally || state.requiredByLoadingItem});
- stateStack->front().pendingResolvedDependencies.push(
+ stateStack().front().pendingResolvedDependencies.push(
FullyResolvedDependsItem::makeBaseDependency());
break;
} catch (const ErrorInfo &e) {
if (dependency.name.toString() == StringConstants::qbsModule())
throw e;
- // This can happen when a property is set unconditionally on a non-required,
- // non-present dependency. We allow this for user convenience.
- if (!dependency.requiredLocally) {
- state.pendingResolvedDependencies.pop();
- continue;
- }
-
// See QBS-1338 for why we do not abort handling the product.
state.pendingResolvedDependencies.pop();
- Item::Modules &modules = product->item->modules();
+ Item::Modules &modules = m_product.item->modules();
// Unwind.
- while (stateStack->size() > 1) {
+ while (stateStack().size() > 1) {
const auto loadingItemModule = std::find_if(
modules.begin(), modules.end(), [&](const Item::Module &m) {
- return m.item == stateStack->front().loadingItem;
+ return m.item == stateStack().front().loadingItem;
});
for (auto it = loadingItemModule; it != modules.end(); ++it) {
- createNonPresentModule(loaderState.itemPool(), it->name.toString(),
+ createNonPresentModule(m_loaderState.itemPool(), it->name.toString(),
QLatin1String("error in Depends chain"), it->item);
}
modules.erase(loadingItemModule, modules.end());
- stateStack->pop_front();
+ stateStack().pop_front();
}
- product->handleError(e);
+ m_product.handleError(e);
return HandleDependency::Ignore;
}
}
@@ -434,7 +390,7 @@ HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
// 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::Private::loadModule(
+LoadModuleResult DependenciesResolver::loadModule(
Item *loadingItem, const FullyResolvedDependsItem &dependency)
{
qCDebug(lcModuleLoader) << "loadModule name:" << dependency.name.toString()
@@ -445,9 +401,15 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
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, product->item);
+ = findExistingModule(dependency, m_product.item);
if (existingModule) {
// Merge version range and required property. These will be checked again
// after probes resolving.
@@ -456,11 +418,19 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
QBS_CHECK(existingModule->item);
moduleItem = existingModule->item;
- if (!contains(existingModule->loadingItems, loadingItem))
- existingModule->loadingItems.push_back(loadingItem);
+ 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))) {
+ } else if (productDep = findMatchingProduct(dependency); !productDep) {
moduleItem = findMatchingModule(dependency);
}
@@ -475,7 +445,7 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
if (checkResult.first) {
QBS_CHECK(productDep->mergedExportItem);
- moduleItem = productDep->mergedExportItem->clone();
+ moduleItem = productDep->mergedExportItem->clone(m_loaderState.itemPool());
moduleItem->setParent(nullptr);
// Needed for isolated Export item evaluation.
@@ -496,17 +466,16 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
// The loading name is only used to ensure consistent sorting in case of equal
// value priorities; see ModulePropertyMerger.
QString loadingName;
- if (loadingItem == product->item) {
- loadingName = product->name;
- } else if (!stateStack->empty()) {
- const auto &loadingItemOrigin = stateStack->front().loadingItemOrigin;
+ 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;
}
- loaderState.moduleInstantiator().instantiate({
- product->item, product->name, loadingItem, loadingName, moduleItem, moduleWithSameName,
- productDep ? productDep->item : nullptr, product->scope, product->project->scope,
- dependency.name, dependency.id(), bool(existingModule)});
+ 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
@@ -532,26 +501,18 @@ LoadModuleResult DependenciesResolver::Private::loadModule(
}
qCDebug(lcModuleLoader) << "module loaded:" << dependency.name.toString();
- if (product->item) {
+ if (m_product.item) {
Item::Module module = createModule(dependency, moduleItem, productDep);
-
- if (module.name.toString() != StringConstants::qbsModule()) {
- // TODO: Why do we have default parameters only for Export items and
- // property declarations only for modules? Does that make any sense?
- if (productDep)
- module.parameters = productDep->defaultParameters;
- mergeParameters(module.parameters, dependency.parameters);
- }
module.required = dependency.requiredGlobally;
- module.loadingItems.push_back(loadingItem);
- module.maxDependsChainLength = stateStack->size();
- product->item->addModule(module);
+ addLoadContext(module);
+ module.maxDependsChainLength = dependsChainLength();
+ m_product.item->addModule(module);
addLocalModule();
}
return {moduleItem, nullptr, HandleDependency::Use};
}
-std::pair<Item::Module *, Item *> DependenciesResolver::Private::findExistingModule(
+std::pair<Item::Module *, Item *> DependenciesResolver::findExistingModule(
const FullyResolvedDependsItem &dependency, Item *item)
{
if (!item) // Happens if and only if called via loadBaseModule().
@@ -560,12 +521,12 @@ std::pair<Item::Module *, Item *> DependenciesResolver::Private::findExistingMod
for (Item::Module &m : item->modules()) {
if (m.name != dependency.name)
continue;
- if (!m.productInfo) {
+ if (!m.product) {
QBS_CHECK(!dependency.product);
return {&m, m.item};
}
- if ((dependency.profile.isEmpty() || (m.productInfo->profile == dependency.profile))
- && m.productInfo->multiplexId == dependency.multiplexId) {
+ if ((dependency.profile.isEmpty() || (m.product->profileName == dependency.profile))
+ && m.product->multiplexConfigurationId == dependency.multiplexId) {
return {&m, m.item};
}
@@ -576,39 +537,39 @@ std::pair<Item::Module *, Item *> DependenciesResolver::Private::findExistingMod
return {nullptr, moduleWithSameName};
}
-void DependenciesResolver::Private::updateModule(
+void DependenciesResolver::updateModule(
Item::Module &module, const FullyResolvedDependsItem &dependency)
{
- moduleLoader.forwardParameterDeclarations(dependency.item, product->item->modules());
-
- // TODO: Use priorities like for property values. See QBS-1300.
- mergeParameters(module.parameters, dependency.parameters);
-
+ forwardParameterDeclarations(dependency.item, m_product.item->modules());
module.versionRange.narrowDown(dependency.versionRange);
module.required |= dependency.requiredGlobally;
- if (int(stateStack->size()) > module.maxDependsChainLength)
- module.maxDependsChainLength = stateStack->size();
+ if (dependsChainLength() > module.maxDependsChainLength)
+ module.maxDependsChainLength = dependsChainLength();
}
-ProductContext *DependenciesResolver::Private::findMatchingProduct(
+int DependenciesResolver::dependsChainLength()
+{
+ return m_product.dependenciesContext ? stateStack().size() : 1;
+}
+
+ProductContext *DependenciesResolver::findMatchingProduct(
const FullyResolvedDependsItem &dependency)
{
- const auto candidates = product->project->topLevelProject
- ->productsByName.equal_range(dependency.name.toString());
- for (auto it = candidates.first; it != candidates.second; ++it) {
- ProductContext * const candidate = it->second;
- if (candidate->multiplexConfigurationId != dependency.multiplexId)
- continue;
- if (!dependency.profile.isEmpty() && dependency.profile != candidate->profileName)
- continue;
- if (dependency.limitToSubProject && !haveSameSubProject(*product, *candidate))
- continue;
- return candidate;
- }
- return nullptr;
+ 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::Private::findMatchingModule(
+Item *DependenciesResolver::findMatchingModule(
const FullyResolvedDependsItem &dependency)
{
// If we can tell that this is supposed to be a product dependency, we can skip
@@ -618,40 +579,38 @@ Item *DependenciesResolver::Private::findMatchingModule(
if (!dependency.profile.isEmpty()) {
throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
"for the requested profile '%3'.")
- .arg(product->displayName(), dependency.displayName(),
+ .arg(m_product.displayName(), dependency.displayName(),
dependency.profile),
- product->item->location());
+ m_product.item->location());
}
throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
- .arg(product->displayName(), dependency.displayName()),
- product->item->location());
+ .arg(m_product.displayName(), dependency.displayName()),
+ m_product.item->location());
}
return nullptr;
}
- const ModuleLoader::ProductContext loaderContext{
- product->item, product->project->item, product->name, product->uniqueName(),
- product->profileName, product->multiplexConfigurationId, product->moduleProperties,
- product->profileModuleProperties};
- const ModuleLoader::Result loaderResult = moduleLoader.searchAndLoadModuleFile(
- loaderContext, dependency.location(), dependency.name, dependency.fallbackMode,
- dependency.requiredGlobally);
-
- Item *moduleItem = loaderResult.moduleItem;
- product->info.probes << loaderResult.providerProbes;
- if (moduleItem) {
+ if (Item *moduleItem = searchAndLoadModuleFile(
+ m_loaderState, m_product, dependency.location(), dependency.name)) {
+ QBS_CHECK(moduleItem->type() == ItemType::Module);
Item * const proto = moduleItem;
- moduleItem = moduleItem->clone();
+ ModuleItemLocker locker(*moduleItem);
+ moduleItem = moduleItem->clone(m_loaderState.itemPool());
moduleItem->setPrototype(proto); // For parameter declarations.
- } else if (dependency.requiredGlobally) {
- throw ErrorInfo(Tr::tr("Dependency '%1' not found for product '%2'.")
- .arg(dependency.name.toString(), product->displayName()),
- dependency.location());
+ return moduleItem;
}
- 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::Private::checkProductDependency(
+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
@@ -659,20 +618,18 @@ std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDep
if (!depSpec.checkProduct)
return {true, HandleDependency::Use};
- if (&dep == product) {
+ if (&dep == &m_product) {
throw ErrorInfo(Tr::tr("Dependency '%1' refers to itself.").arg(depSpec.name.toString()),
depSpec.location());
}
- if (any_of(product->project->topLevelProject->productsToHandle, [&dep](const auto &e) {
- return e.first == &dep;
- })) {
- if (deferral == Deferral::Allowed)
+ 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(product->displayName()), product->item->location());
+ .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());
@@ -681,12 +638,12 @@ std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDep
// This covers both the case of user-disabled products and products with errors.
// The latter are force-disabled in ProductContext::handleError().
- if (product->project->topLevelProject->disabledItems.contains(dep.item)) {
+ if (m_product.project->topLevelProject->isDisabledItem(dep.item)) {
if (depSpec.requiredGlobally) {
ErrorInfo e;
e.append(Tr::tr("Product '%1' depends on '%2',")
- .arg(product->displayName(), dep.displayName()),
- product->item->location());
+ .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;
@@ -696,10 +653,15 @@ std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDep
return {true, HandleDependency::Use};
}
-void DependenciesResolver::Private::checkModule(
+void DependenciesResolver::checkModule(
const FullyResolvedDependsItem &dependency, Item *moduleItem, ProductContext *productDep)
{
- for (auto it = stateStack->begin(); it != stateStack->end(); ++it) {
+ // 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)
@@ -714,11 +676,11 @@ void DependenciesResolver::Private::checkModule(
e.append(it->loadingItemOrigin.name.toString(),
it->loadingItemOrigin.location());
if (it->loadingItem->type() == ItemType::ModuleInstance) {
- createNonPresentModule(loaderState.itemPool(),
+ createNonPresentModule(m_loaderState.itemPool(),
it->loadingItemOrigin.name.toString(),
QLatin1String("cyclic dependency"), it->loadingItem);
}
- if (it == stateStack->begin())
+ if (it == stateStack().begin())
break;
--it;
}
@@ -728,44 +690,30 @@ void DependenciesResolver::Private::checkModule(
checkForModuleNamePrefixCollision(dependency);
}
-void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *dependsItem)
+void DependenciesResolver::adjustDependsItemForMultiplexing(Item *dependsItem)
{
- Evaluator &evaluator = loaderState.evaluator();
+ if (m_product.name.startsWith(StringConstants::shadowProductPrefix()))
+ return;
+
+ Evaluator &evaluator = m_loaderState.evaluator();
const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty());
- const bool productIsMultiplexed = !product->multiplexConfigurationId.isEmpty();
- if (name == product->name) {
+ const bool productIsMultiplexed = !m_product.multiplexConfigurationId.isEmpty();
+ if (name == m_product.name) {
QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator.
return;
}
- const auto productRange = product->project->topLevelProject->productsByName.equal_range(name);
- if (productRange.first == productRange.second)
- return; // Dependency is a module. Nothing to adjust.
- bool profilesPropertyIsSet;
- const QStringList profiles = evaluator.stringListValue(
- dependsItem, StringConstants::profilesProperty(), &profilesPropertyIsSet);
-
- std::vector<const ProductContext *> multiplexedDependencies;
bool hasNonMultiplexedDependency = false;
- for (auto it = productRange.first; it != productRange.second; ++it) {
- if (!it->second->multiplexConfigurationId.isEmpty())
- multiplexedDependencies.push_back(it->second);
- else
+ const std::vector<ProductContext *> multiplexedDependencies = m_product.project
+ ->topLevelProject->productsWithNameAndConstraint(name, [&hasNonMultiplexedDependency]
+ (const ProductContext &product) {
+ if (product.multiplexConfigurationId.isEmpty()) {
hasNonMultiplexedDependency = true;
- }
- bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
-
- 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 && lhsProperty.value() != rhsProperty.value())
- return false;
+ return false;
}
return true;
- };
+ });
+ const bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
// These are the allowed cases:
// (1) Normal dependency with no multiplexing whatsoever.
@@ -783,35 +731,38 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
// 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.
- // (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 (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 ShadowProductInfo shadowProductInfo = getShadowProductInfo(*product);
- const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name;
- const auto productMultiplexConfig
- = ProductItemMultiplexer::multiplexIdToVariantMap(product->multiplexConfigurationId);
+ const auto productMultiplexConfig = m_loaderState.topLevelProject().multiplexConfiguration(
+ m_product.multiplexConfigurationId);
for (const ProductContext *dependency : multiplexedDependencies) {
- const bool depMatchesShadowProduct = isShadowProduct
- && dependency->item == product->item->parent();
- const QString depMultiplexId = dependency->multiplexConfigurationId;
- if (depMatchesShadowProduct) { // (5)
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- VariantValue::create(depMultiplexId));
- return;
- }
if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a
- if (dependency->multiplexConfigurationId == product->multiplexConfigurationId) {
- const ValuePtr &multiplexId = product->item->property(
+ if (dependency->multiplexConfigurationId == m_product.multiplexConfigurationId) {
+ const ValuePtr &multiplexId = m_product.item->property(
StringConstants::multiplexConfigurationIdProperty());
dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
multiplexId);
@@ -819,8 +770,8 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
}
// Otherwise collect partial matches and decide later
- const auto dependencyMultiplexConfig = ProductItemMultiplexer::multiplexIdToVariantMap(
- dependency->multiplexConfigurationId);
+ const auto dependencyMultiplexConfig = m_loaderState.topLevelProject()
+ .multiplexConfiguration(dependency->multiplexConfigurationId);
if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig))
multiplexIds << dependency->multiplexConfigurationId;
@@ -829,12 +780,11 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
const bool profileMatch = !profilesPropertyIsSet || profiles.empty()
|| profiles.contains(dependency->profileName);
if (profileMatch)
- multiplexIds << depMultiplexId;
+ multiplexIds << dependency->multiplexConfigurationId;
}
}
if (multiplexIds.empty()) {
- const QString productName = ProductItemMultiplexer::fullProductDisplayName(
- product->name, product->multiplexConfigurationId);
+ 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),
@@ -845,11 +795,11 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) {
QStringList candidateNames;
for (const auto &id : std::as_const(multiplexIds))
- candidateNames << ProductItemMultiplexer::fullProductDisplayName(name, id);
+ candidateNames << fullProductDisplayName(name, id);
throw ErrorInfo(
Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. "
"Eligible multiplex candidates: %3.").arg(
- product->displayName(), name, candidateNames.join(QLatin1String(", "))),
+ m_product.displayName(), name, candidateNames.join(QLatin1String(", "))),
dependsItem->location());
}
@@ -858,10 +808,10 @@ void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *depen
}
-std::optional<EvaluatedDependsItem> DependenciesResolver::Private::evaluateDependsItem(Item *item)
+std::optional<EvaluatedDependsItem> DependenciesResolver::evaluateDependsItem(Item *item)
{
- Evaluator &evaluator = loaderState.evaluator();
- if (!product->project->topLevelProject->checkItemCondition(item, evaluator)) {
+ Evaluator &evaluator = m_loaderState.evaluator();
+ if (!m_product.project->topLevelProject->checkItemCondition(item, evaluator)) {
qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
return {};
}
@@ -897,11 +847,6 @@ std::optional<EvaluatedDependsItem> DependenciesResolver::Private::evaluateDepen
item->location());
}
- const FallbackMode fallbackMode
- = loaderState.parameters().fallbackProviderEnabled()
- && evaluator.boolValue(item, StringConstants::enableFallbackProperty())
- ? FallbackMode::Enabled : FallbackMode::Disabled;
-
bool profilesPropertyWasSet = false;
std::optional<QStringList> profiles;
bool required = true;
@@ -921,35 +866,39 @@ std::optional<EvaluatedDependsItem> DependenciesResolver::Private::evaluateDepen
const QStringList multiplexIds = evaluator.stringListValue(
item, StringConstants::multiplexConfigurationIdsProperty());
adjustParametersScopes(item, item);
- moduleLoader.forwardParameterDeclarations(item, product->item->modules());
+ 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, FileTags::fromStringList(productTypes),
- multiplexIds, profiles, {minVersion, maxVersion}, parameters, limitToSubProject,
- fallbackMode, required};
+ 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::Private::multiplexDependency(const EvaluatedDependsItem &dependency)
+DependenciesResolver::multiplexDependency(const EvaluatedDependsItem &dependency)
{
std::queue<FullyResolvedDependsItem> dependencies;
if (!dependency.productTypes.empty()) {
- std::vector<ProductContext *> matchingProducts;
- for (const FileTag &typeTag : dependency.productTypes) {
- const auto range = product->project->topLevelProject->productsByType.equal_range(typeTag);
- for (auto it = range.first; it != range.second; ++it) {
- if (it->second != product
- && it->second->name != product->name
- && (!dependency.limitToSubProject
- || haveSameSubProject(*product, *it->second))) {
- matchingProducts.push_back(it->second);
- }
- }
- }
+ 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();
@@ -979,80 +928,106 @@ DependenciesResolver::Private::multiplexDependency(const EvaluatedDependsItem &d
return dependencies;
}
-void DependenciesResolver::Private::setSearchPathsForProduct()
-{
- QBS_CHECK(product->searchPaths.isEmpty());
-
- product->searchPaths = loaderState.itemReader().readExtraSearchPaths(product->item);
- Settings settings(loaderState.parameters().settingsDirectory());
- const QStringList prefsSearchPaths = Preferences(&settings, product->profileModuleProperties)
- .searchPaths();
- const QStringList &currentSearchPaths = loaderState.itemReader().allSearchPaths();
- for (const QString &p : prefsSearchPaths) {
- if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
- product->searchPaths << p;
- }
-}
-
-QVariantMap DependenciesResolver::Private::extractParameters(Item *dependsItem) const
+QVariantMap DependenciesResolver::extractParameters(Item *dependsItem) const
{
- QVariantMap result;
- const Item::PropertyMap &itemProperties = filterItemProperties(dependsItem->properties());
- if (itemProperties.empty())
- return result;
- auto origProperties = dependsItem->properties();
-
- // TODO: This is not exception-safe. Also, can't we do the item value check along the
- // way, without allocationg an extra map and exchanging the list of children?
- dependsItem->setProperties(itemProperties);
-
- JSValue sv = loaderState.evaluator().scriptValue(dependsItem);
try {
- result = safeToVariant(loaderState.evaluator().engine()->context(), sv);
+ 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;
}
- dependsItem->setProperties(origProperties);
- return result;
}
-void DependenciesResolver::Private::checkForModuleNamePrefixCollision(
+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 (!product->item)
+ if (!m_product.item)
return;
- for (const Item::Module &m : product->item->modules()) {
- if (m.name.length() == dependency.name.length()
- || m.name.front() != dependency.name.front()) {
+ for (const Item::Module &m : m_product.item->modules()) {
+ if (m.name.length() == dependency.name.length())
continue;
- }
+
QualifiedId shortName;
QualifiedId longName;
- if (m.name < dependency.name) {
+ if (m.name.length() < dependency.name.length()) {
shortName = m.name;
longName = dependency.name;
} else {
shortName = dependency.name;
longName = m.name;
}
- 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")
+ 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::Private::createModule(
+Item::Module DependenciesResolver::createModule(
const FullyResolvedDependsItem &dependency, Item *item, ProductContext *productDep)
{
Item::Module m;
m.item = item;
- if (productDep) {
- m.productInfo.emplace(productDep->item, productDep->multiplexConfigurationId,
- productDep->profileName);
- }
+ m.product = productDep;
m.name = dependency.name;
m.required = dependency.requiredLocally;
m.versionRange = dependency.versionRange;
@@ -1061,28 +1036,33 @@ Item::Module DependenciesResolver::Private::createModule(
FullyResolvedDependsItem::FullyResolvedDependsItem(
ProductContext *product, const EvaluatedDependsItem &dependency)
- : product(product), item(dependency.item), name(product->name),
- versionRange(dependency.versionRange), parameters(dependency.parameters),
- fallbackMode(FallbackMode::Disabled), checkProduct(false) {}
+ : product(product)
+ , item(dependency.item)
+ , name(product->name)
+ , versionRange(dependency.versionRange)
+ , parameters(dependency.parameters)
+ , checkProduct(false)
+{}
FullyResolvedDependsItem FullyResolvedDependsItem::makeBaseDependency()
{
FullyResolvedDependsItem item;
- item.fallbackMode = FallbackMode::Disabled;
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),
- fallbackMode(dependency.fallbackMode),
- requiredLocally(dependency.requiredLocally),
- requiredGlobally(dependency.requiredGlobally) {}
+ : 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
{
@@ -1104,7 +1084,7 @@ CodeLocation FullyResolvedDependsItem::location() const
QString FullyResolvedDependsItem::displayName() const
{
- return ProductItemMultiplexer::fullProductDisplayName(name.toString(), multiplexId);
+ return fullProductDisplayName(name.toString(), multiplexId);
}
bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2)
@@ -1117,16 +1097,6 @@ bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2)
return false;
}
-Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties)
-{
- Item::PropertyMap result;
- for (auto it = properties.begin(); it != properties.end(); ++it) {
- if (it.value()->type() == Value::ItemValueType)
- result.insert(it.key(), it.value());
- }
- return result;
-}
-
QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
{
QVariantMap result;
@@ -1141,4 +1111,65 @@ QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
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 &currentDependsItem = 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 &currentSearchPaths = 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
index 0b6d57023..48502adb7 100644
--- a/src/lib/corelib/loader/dependenciesresolver.h
+++ b/src/lib/corelib/loader/dependenciesresolver.h
@@ -39,53 +39,19 @@
#pragma once
-#include <tools/pimpl.h>
-#include <tools/set.h>
-
-#include <QtGlobal>
-
-#include <functional>
-
-QT_BEGIN_NAMESPACE
-class QString;
-QT_END_NAMESPACE
-
namespace qbs::Internal {
class Item;
class LoaderState;
class ProductContext;
-class StoredModuleProviderInfo;
enum class Deferral;
// Collects the products' dependencies and builds the list of modules from them.
// Actual loading of module files is offloaded to ModuleLoader.
-class DependenciesResolver
-{
-public:
- DependenciesResolver(LoaderState &loaderState);
- ~DependenciesResolver();
-
- // Returns false if the product has unhandled product dependencies and thus needs
- // to be deferred, true otherwise.
- bool resolveDependencies(ProductContext &product, Deferral deferral);
-
- void checkDependencyParameterDeclarations(const Item *productItem,
- const QString &productName) const;
-
- void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
- StoredModuleProviderInfo storedModuleProviderInfo() const;
- const Set<QString> &tempQbsFiles() const;
-
- void printProfilingInfo(int indent);
-
- // 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);
+void resolveDependencies(ProductContext &product, Deferral deferral, LoaderState &loaderState);
-private:
- class Private;
- Pimpl<Private> d;
-};
+// 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
index a0cab5367..3dbfad352 100644
--- a/src/lib/corelib/loader/groupshandler.cpp
+++ b/src/lib/corelib/loader/groupshandler.cpp
@@ -51,66 +51,47 @@
#include <tools/stringconstants.h>
namespace qbs::Internal {
-class GroupsHandler::Private
+class GroupsHandler
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
+ 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(Item *product, Item *productScope,
- const Item::Module &module);
- void moveGroupsFromModulesToProduct(Item *product, Item *productScope);
+ void moveGroupsFromModuleToProduct(const Item::Module &module);
+ void moveGroupsFromModulesToProduct();
void propagateModulesFromParent(Item *group);
- void handleGroup(Item *product, Item *group);
+ void handleGroup(Item *group);
void adjustScopesInGroupModuleInstances(Item *groupItem, const Item::Module &module);
QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group);
- LoaderState &loaderState;
- std::unordered_map<const Item *, QualifiedIdSet> modulePropsSetInGroups;
- Set<Item *> disabledGroups;
- qint64 elapsedTime = 0;
+ ProductContext &m_product;
+ LoaderState &m_loaderState;
};
-GroupsHandler::GroupsHandler(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) {}
-GroupsHandler::~GroupsHandler() = default;
-
-void GroupsHandler::setupGroups(Item *product, Item *productScope)
+void setupGroups(ProductContext &product, LoaderState &loaderState)
{
- AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTime : nullptr);
-
- d->modulePropsSetInGroups.clear();
- d->disabledGroups.clear();
- d->moveGroupsFromModulesToProduct(product, productScope);
- for (Item * const child : product->children()) {
- if (child->type() == ItemType::Group)
- d->handleGroup(product, child);
- }
+ GroupsHandler(product, loaderState).run();
}
-std::unordered_map<const Item *, QualifiedIdSet> GroupsHandler::modulePropertiesSetInGroups() const
+void GroupsHandler::run()
{
- return d->modulePropsSetInGroups;
-}
+ AccumulatingTimer timer(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.groupsSetup : nullptr);
-Set<Item *> GroupsHandler::disabledGroups() const
-{
- return d->disabledGroups;
-}
-
-void GroupsHandler::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << QByteArray(indent, ' ')
- << Tr::tr("Setting up Groups took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
+ moveGroupsFromModulesToProduct();
+ for (Item * const child : m_product.item->children()) {
+ if (child->type() == ItemType::Group)
+ handleGroup(child);
+ }
}
-void GroupsHandler::Private::gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
+void GroupsHandler::gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
QualifiedIdSet &properties)
{
const Item::PropertyMap &props = iv->item()->properties();
@@ -131,10 +112,10 @@ void GroupsHandler::Private::gatherAssignedProperties(ItemValue *iv, const Quali
}
}
-void GroupsHandler::Private::markModuleTargetGroups(Item *group, const Item::Module &module)
+void GroupsHandler::markModuleTargetGroups(Item *group, const Item::Module &module)
{
QBS_CHECK(group->type() == ItemType::Group);
- if (loaderState.evaluator().boolValue(group, StringConstants::filesAreTargetsProperty())) {
+ if (m_loaderState.evaluator().boolValue(group, StringConstants::filesAreTargetsProperty())) {
group->setProperty(StringConstants::modulePropertyInternal(),
VariantValue::create(module.name.toString()));
}
@@ -142,8 +123,7 @@ void GroupsHandler::Private::markModuleTargetGroups(Item *group, const Item::Mod
markModuleTargetGroups(child, module);
}
-void GroupsHandler::Private::moveGroupsFromModuleToProduct(Item *product, Item *productScope,
- const Item::Module &module)
+void GroupsHandler::moveGroupsFromModuleToProduct(const Item::Module &module)
{
if (!module.item->isPresentModule())
return;
@@ -153,37 +133,41 @@ void GroupsHandler::Private::moveGroupsFromModuleToProduct(Item *product, Item *
++it;
continue;
}
- child->setScope(productScope);
- setScopeForDescendants(child, productScope);
- Item::addChild(product, child);
+
+ 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::Private::moveGroupsFromModulesToProduct(Item *product, Item *productScope)
+void GroupsHandler::moveGroupsFromModulesToProduct()
{
- for (const Item::Module &module : product->modules())
- moveGroupsFromModuleToProduct(product, productScope, module);
+ 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::Private::propagateModulesFromParent(Item *group)
+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 = loaderState.moduleInstantiator()
- .retrieveModuleInstanceItem(group, m.name);
+ Item * const targetItem = retrieveModuleInstanceItem(group, m.name, m_loaderState);
QBS_CHECK(targetItem->type() == ItemType::ModuleInstancePlaceholder);
targetItem->setPrototype(m.item);
- Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope);
+ 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);
@@ -210,7 +194,8 @@ void GroupsHandler::Private::propagateModulesFromParent(Item *group)
adaptedModules << depMod;
if (depMod.name.front() == module.name.front())
continue;
- const ItemValuePtr &modulePrefix = group->itemProperty(depMod.name.front());
+ const ItemValuePtr &modulePrefix = group->itemProperty(depMod.name.front(),
+ m_loaderState.itemPool());
QBS_CHECK(modulePrefix);
module.item->setProperty(depMod.name.front(), modulePrefix);
}
@@ -220,7 +205,7 @@ void GroupsHandler::Private::propagateModulesFromParent(Item *group)
const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(group);
if (propsSetInGroup.empty())
return;
- modulePropsSetInGroups.insert(std::make_pair(group, propsSetInGroup));
+ 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.
@@ -228,18 +213,17 @@ void GroupsHandler::Private::propagateModulesFromParent(Item *group)
adjustScopesInGroupModuleInstances(group, module);
}
-void GroupsHandler::Private::handleGroup(Item *product, Item *group)
+void GroupsHandler::handleGroup(Item *group)
{
propagateModulesFromParent(group);
- if (!loaderState.evaluator().boolValue(group, StringConstants::conditionProperty()))
- disabledGroups << group;
+ m_loaderState.topLevelProject().checkItemCondition(group, m_loaderState.evaluator());
for (Item * const child : group->children()) {
if (child->type() == ItemType::Group)
- handleGroup(product, child);
+ handleGroup(child);
}
}
-void GroupsHandler::Private::adjustScopesInGroupModuleInstances(Item *groupItem,
+void GroupsHandler::adjustScopesInGroupModuleInstances(Item *groupItem,
const Item::Module &module)
{
if (!module.item->isPresentModule())
@@ -279,7 +263,7 @@ void GroupsHandler::Private::adjustScopesInGroupModuleInstances(Item *groupItem,
continue;
if (propValue->scope())
- module.item->setProperty(propName, propValue->clone());
+ module.item->setProperty(propName, propValue->clone(m_loaderState.itemPool()));
}
for (const ValuePtr &prop : module.item->properties()) {
@@ -300,7 +284,7 @@ void GroupsHandler::Private::adjustScopesInGroupModuleInstances(Item *groupItem,
}
}
-QualifiedIdSet GroupsHandler::Private::gatherModulePropertiesSetInGroup(const Item *group)
+QualifiedIdSet GroupsHandler::gatherModulePropertiesSetInGroup(const Item *group)
{
QualifiedIdSet propsSetInGroup;
const Item::PropertyMap &props = group->properties();
diff --git a/src/lib/corelib/loader/groupshandler.h b/src/lib/corelib/loader/groupshandler.h
index ff6afdad9..5de94ce7c 100644
--- a/src/lib/corelib/loader/groupshandler.h
+++ b/src/lib/corelib/loader/groupshandler.h
@@ -39,16 +39,9 @@
#pragma once
-#include <language/qualifiedid.h>
-#include <tools/set.h>
-#include <tools/pimpl.h>
-
-#include <unordered_map>
-#include <utility>
-
namespace qbs::Internal {
-class Item;
class LoaderState;
+class ProductContext;
// Sets up Group items for the actual resolving stage. Responsibilities:
// - Moving Group items located in modules over to the product.
@@ -58,21 +51,6 @@ class LoaderState;
// - 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).
-class GroupsHandler
-{
-public:
- GroupsHandler(LoaderState &loaderState);
- ~GroupsHandler();
-
- void setupGroups(Item *product, Item *productScope);
- std::unordered_map<const Item *, QualifiedIdSet> modulePropertiesSetInGroups() const;
- Set<Item *> disabledGroups() const;
-
- void printProfilingInfo(int indent);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+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
index 8d9b2ccd4..0638d1af5 100644
--- a/src/lib/corelib/loader/itemreader.cpp
+++ b/src/lib/corelib/loader/itemreader.cpp
@@ -73,7 +73,8 @@ ItemReader::ItemReader(LoaderState &loaderState) : m_loaderState(loaderState) {}
void ItemReader::init()
{
- m_visitorState = std::make_unique<ItemReaderVisitorState>(m_loaderState.logger());
+ 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());
@@ -150,11 +151,6 @@ Item *ItemReader::readFile(const QString &filePath, const CodeLocation &referenc
}
}
-Set<QString> ItemReader::filesRead() const
-{
- return m_visitorState->filesRead();
-}
-
void ItemReader::handlePropertyOptions(Item *optionsItem)
{
Evaluator &evaluator = m_loaderState.evaluator();
@@ -218,6 +214,14 @@ void ItemReader::handleAllPropertyOptionsItems(Item *item)
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;
}
@@ -226,7 +230,7 @@ Item *ItemReader::wrapInProjectIfNecessary(Item *item)
{
if (item->type() == ItemType::Project)
return item;
- Item *prj = Item::create(item->pool(), ItemType::Project);
+ Item *prj = Item::create(&m_loaderState.itemPool(), ItemType::Project);
Item::addChild(prj, item);
prj->setFile(item->file());
prj->setLocation(item->location());
diff --git a/src/lib/corelib/loader/itemreader.h b/src/lib/corelib/loader/itemreader.h
index 01e9ae18d..e444db16c 100644
--- a/src/lib/corelib/loader/itemreader.h
+++ b/src/lib/corelib/loader/itemreader.h
@@ -87,8 +87,6 @@ public:
Item *wrapInProjectIfNecessary(Item *item);
QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr);
- Set<QString> filesRead() const;
-
qint64 elapsedTime() const { return m_elapsedTime; }
private:
diff --git a/src/lib/corelib/loader/itemreaderastvisitor.cpp b/src/lib/corelib/loader/itemreaderastvisitor.cpp
index 48bd0af1b..fe0d6c190 100644
--- a/src/lib/corelib/loader/itemreaderastvisitor.cpp
+++ b/src/lib/corelib/loader/itemreaderastvisitor.cpp
@@ -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) {
@@ -241,7 +250,7 @@ bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast)
throw ErrorInfo(Tr::tr("id: must be followed by identifier"));
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());
@@ -335,6 +344,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) {
diff --git a/src/lib/corelib/loader/itemreaderastvisitor.h b/src/lib/corelib/loader/itemreaderastvisitor.h
index a102b2821..c47501f2f 100644
--- a/src/lib/corelib/loader/itemreaderastvisitor.h
+++ b/src/lib/corelib/loader/itemreaderastvisitor.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,6 +93,7 @@ private:
Logger &m_logger;
QHash<QStringList, QString> m_typeNameToFile;
Item *m_item = nullptr;
+ std::unique_ptr<ModuleItemLocker> m_moduleItemLocker;
ItemType m_instanceItemType = ItemType::ModuleInstancePlaceholder;
};
diff --git a/src/lib/corelib/loader/itemreadervisitorstate.cpp b/src/lib/corelib/loader/itemreadervisitorstate.cpp
index 57484043a..6ea23c021 100644
--- a/src/lib/corelib/loader/itemreadervisitorstate.cpp
+++ b/src/lib/corelib/loader/itemreadervisitorstate.cpp
@@ -39,6 +39,7 @@
#include "itemreadervisitorstate.h"
#include "itemreaderastvisitor.h"
+#include "loaderutils.h"
#include <language/asttools.h>
#include <language/filecontext.h>
@@ -47,89 +48,35 @@
#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 std::unordered_map<QString, ASTCacheValue> {};
-
-
-ItemReaderVisitorState::ItemReaderVisitorState(Logger &logger)
- : m_logger(logger)
- , m_astCache(std::make_unique<ASTCache>())
-{
-
}
-ItemReaderVisitorState::~ItemReaderVisitorState() = default;
-
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);
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()) {
@@ -142,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)
-{
- m_directoryEntries.insert(dirPath, entries);
-}
-
-bool ItemReaderVisitorState::findDirectoryEntries(const QString &dirPath, QStringList *entries) const
+void 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/loader/itemreadervisitorstate.h b/src/lib/corelib/loader/itemreadervisitorstate.h
index dc22cfb42..cbf1966b6 100644
--- a/src/lib/corelib/loader/itemreadervisitorstate.h
+++ b/src/lib/corelib/loader/itemreadervisitorstate.h
@@ -50,21 +50,19 @@ namespace qbs {
namespace Internal {
class Item;
class ItemPool;
+class ItemReaderCache;
class Logger;
class ItemReaderVisitorState
{
public:
- ItemReaderVisitorState(Logger &logger);
- ~ItemReaderVisitorState();
+ ItemReaderVisitorState(ItemReaderCache &cache, Logger &logger);
Logger &logger() { return m_logger; }
- Set<QString> filesRead() const { return m_filesRead; }
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);
@@ -74,13 +72,9 @@ public:
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;
- const std::unique_ptr<ASTCache> m_astCache;
};
} // namespace Internal
diff --git a/src/lib/corelib/loader/loaderutils.cpp b/src/lib/corelib/loader/loaderutils.cpp
index 98ac10a92..05a077dbe 100644
--- a/src/lib/corelib/loader/loaderutils.cpp
+++ b/src/lib/corelib/loader/loaderutils.cpp
@@ -39,47 +39,33 @@
#include "loaderutils.h"
-#include "dependenciesresolver.h"
#include "itemreader.h"
-#include "localprofiles.h"
-#include "moduleinstantiator.h"
-#include "modulepropertymerger.h"
-#include "probesresolver.h"
-#include "productitemmultiplexer.h"
#include <language/evaluator.h>
-#include <language/item.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 {
-void mergeParameters(QVariantMap &dst, const QVariantMap &src)
+QString fullProductDisplayName(const QString &name, const QString &multiplexId)
{
- for (auto it = src.begin(); it != src.end(); ++it) {
- if (it.value().userType() == QMetaType::QVariantMap) {
- QVariant &vdst = dst[it.key()];
- QVariantMap mdst = vdst.toMap();
- mergeParameters(mdst, it.value().toMap());
- vdst = mdst;
- } else {
- dst[it.key()] = it.value();
- }
- }
-}
-
-ShadowProductInfo getShadowProductInfo(const ProductContext &product)
-{
- const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix());
- return std::make_pair(isShadowProduct,
- isShadowProduct
- ? product.name.mid(StringConstants::shadowProductPrefix().size())
- : QString());
+ 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)
@@ -102,15 +88,15 @@ QString ProductContext::uniqueName() const
QString ProductContext::displayName() const
{
- return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId);
+ return fullProductDisplayName(name, multiplexConfigurationId);
}
void ProductContext::handleError(const ErrorInfo &error)
{
- const bool alreadyHadError = info.delayedError.hasError();
+ const bool alreadyHadError = delayedError.hasError();
if (!alreadyHadError) {
- info.delayedError.append(Tr::tr("Error while handling product '%1':")
- .arg(name), item->location());
+ delayedError.append(Tr::tr("Error while handling product '%1':")
+ .arg(name), item->location());
}
if (error.isInternalError()) {
if (alreadyHadError) {
@@ -121,76 +107,855 @@ void ProductContext::handleError(const ErrorInfo &error)
}
const auto errorItems = error.items();
for (const ErrorItem &ei : errorItems)
- info.delayedError.append(ei.description(), ei.codeLocation());
- project->topLevelProject->productInfos[item] = info;
- project->topLevelProject->disabledItems << item;
- project->topLevelProject->erroneousProducts.insert(name);
+ 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;
- disabledItems += item;
+ addDisabledItem(item);
return false;
}
-void TopLevelProjectContext::checkCancelation(const SetupProjectParameters &parameters)
+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 &parameters)
+{
+ m_parameters.lock().get().insert({moduleProto, parameters});
+}
+
+QVariantMap TopLevelProjectContext::parameters(Item *moduleProto) const
+{
+ const auto parametersGuard = m_parameters.lock_shared();
+ const auto &parameters = 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
{
- if (progressObserver && progressObserver->canceled()) {
- throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.")
- .arg(TopLevelProject::deriveId(
- parameters.finalBuildConfigurationTree())));
+ 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 &parameters, ItemPool &itemPool,
- Evaluator &evaluator, Logger &logger)
- : parameters(parameters), itemPool(itemPool), evaluator(evaluator), logger(logger),
- itemReader(q), probesResolver(q), propertyMerger(q), localProfiles(q),
- moduleInstantiator(q), dependenciesResolver(q),
- multiplexer(q, [this](Item *productItem) {
- return moduleInstantiator.retrieveQbsItem(productItem);
- })
- {}
+ Private(LoaderState &q, const SetupProjectParameters &parameters,
+ 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 &parameters;
+ TopLevelProjectContext &topLevelProject;
ItemPool &itemPool;
- Evaluator &evaluator;
- Logger &logger;
- TopLevelProjectContext topLevelProject;
+ Logger logger;
ItemReader itemReader;
- ProbesResolver probesResolver;
- ModulePropertyMerger propertyMerger;
- LocalProfiles localProfiles;
- ModuleInstantiator moduleInstantiator;
- DependenciesResolver dependenciesResolver;
- ProductItemMultiplexer multiplexer;
+ Evaluator evaluator;
};
-LoaderState::LoaderState(const SetupProjectParameters &parameters, ItemPool &itemPool,
- Evaluator &evaluator, Logger &logger)
- : d(makePimpl<Private>(*this, parameters, itemPool, evaluator, logger))
+LoaderState::LoaderState(const SetupProjectParameters &parameters,
+ 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; }
-DependenciesResolver &LoaderState::dependenciesResolver() { return d->dependenciesResolver; }
ItemPool &LoaderState::itemPool() { return d->itemPool; }
Evaluator &LoaderState::evaluator() { return d->evaluator; }
Logger &LoaderState::logger() { return d->logger; }
-ModuleInstantiator &LoaderState::moduleInstantiator() { return d->moduleInstantiator; }
-ProductItemMultiplexer &LoaderState::multiplexer() { return d->multiplexer; }
ItemReader &LoaderState::itemReader() { return d->itemReader; }
-LocalProfiles &LoaderState::localProfiles() { return d->localProfiles; }
-ProbesResolver &LoaderState::probesResolver() { return d->probesResolver; }
-ModulePropertyMerger &LoaderState::propertyMerger() { return d->propertyMerger; }
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 &current, 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 &current, const QVariantMap &next, int nextPrio)
+{
+ for (auto it = next.begin(); it != next.end(); ++it) {
+ m_path << it.key();
+ const QVariant &newValue = it.value();
+ QVariant &currentValue = 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
index db05698a5..4a4aadfb8 100644
--- a/src/lib/corelib/loader/loaderutils.h
+++ b/src/lib/corelib/loader/loaderutils.h
@@ -41,46 +41,114 @@
#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 DependenciesResolver;
class Evaluator;
-class Item;
class ItemPool;
class ItemReader;
-class LocalProfiles;
class Logger;
-class ModuleInstantiator;
-class ModulePropertyMerger;
-class ProbesResolver;
class ProductContext;
-class ProductItemMultiplexer;
class ProgressObserver;
class ProjectContext;
+class ScriptEngine;
using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>;
-using ShadowProductInfo = std::pair<bool, QString>;
+using FileLocations = QHash<std::pair<QString, QString>, CodeLocation>;
-enum class FallbackMode { Enabled, Disabled };
enum class Deferral { Allowed, NotAllowed };
+enum class ProductDependency { None, Single, Bulk };
-class ProductInfo
+class CancelException { };
+
+template<typename DataType, typename MutexType = std::shared_mutex>
+struct GuardedData {
+ DataType data;
+ mutable MutexType mutex;
+};
+
+class TimingData
{
public:
- std::vector<ProbeConstPtr> probes;
- ModulePropertiesPerGroup modulePropertiesSetInGroups;
- ErrorInfo delayedError;
+ 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
@@ -89,21 +157,40 @@ 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;
- ProductInfo info;
+ 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;
- bool dependenciesResolved = false;
+ // 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
@@ -112,27 +199,206 @@ public:
TopLevelProjectContext() = default;
TopLevelProjectContext(const TopLevelProjectContext &) = delete;
TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete;
- ~TopLevelProjectContext() { qDeleteAll(projects); }
+ ~TopLevelProjectContext();
bool checkItemCondition(Item *item, Evaluator &evaluator);
- void checkCancelation(const SetupProjectParameters &parameters);
-
- std::vector<ProjectContext *> projects;
- std::list<std::pair<ProductContext *, int>> productsToHandle;
- std::multimap<QString, ProductContext *> productsByName;
- std::unordered_map<Item *, ProductInfo> productInfos;
- Set<QString> projectNamesUsedInOverrides;
- Set<QString> productNamesUsedInOverrides;
- Set<Item *> disabledItems;
- Set<QString> erroneousProducts;
- std::vector<ProbeConstPtr> probes;
- QString buildDirectory;
- QVariantMap profileConfigs;
- ProgressObserver *progressObserver = nullptr;
+ 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 &parameters);
+ 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.
- std::multimap<FileTag, ProductContext *> productsByType;
+ 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
@@ -142,28 +408,36 @@ public:
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 &parameters, ItemPool &itemPool, Evaluator &evaluator,
- Logger &logger);
+ LoaderState(const SetupProjectParameters &parameters, TopLevelProjectContext &topLevelProject,
+ ItemPool &itemPool, ScriptEngine &engine, Logger logger);
~LoaderState();
- DependenciesResolver &dependenciesResolver();
Evaluator &evaluator();
ItemPool &itemPool();
ItemReader &itemReader();
- LocalProfiles &localProfiles();
Logger &logger();
- ModuleInstantiator &moduleInstantiator();
- ProductItemMultiplexer &multiplexer();
const SetupProjectParameters &parameters() const;
- ProbesResolver &probesResolver();
- ModulePropertyMerger &propertyMerger();
TopLevelProjectContext &topLevelProject();
private:
@@ -171,9 +445,20 @@ private:
Pimpl<Private> d;
};
-void mergeParameters(QVariantMap &dst, const QVariantMap &src);
-ShadowProductInfo getShadowProductInfo(const ProductContext &product);
+// 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
index b59204492..e72128fff 100644
--- a/src/lib/corelib/loader/localprofiles.cpp
+++ b/src/lib/corelib/loader/localprofiles.cpp
@@ -52,34 +52,26 @@
#include <tools/stringconstants.h>
namespace qbs::Internal {
-class LocalProfiles::Private
+class LocalProfiles
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
+ 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);
- void collectProfiles(Item *productOrProject, Item *projectScope);
-
- LoaderState &loaderState;
- QVariantMap profiles;
+ LoaderState &m_loaderState;
};
-LocalProfiles::LocalProfiles(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) {}
-LocalProfiles::~LocalProfiles() = default;
-
-void LocalProfiles::collectProfilesFromItems(Item *productOrProject, Item *projectScope)
+void collectProfilesFromItems(Item *productOrProject, Item *projectScope, LoaderState &loaderState)
{
- d->collectProfiles(productOrProject, projectScope);
+ LocalProfiles(loaderState).collectProfiles(productOrProject, projectScope);
}
-const QVariantMap &LocalProfiles::profiles() const
-{
- return d->profiles;
-}
-
-void LocalProfiles::Private::handleProfile(Item *profileItem)
+void LocalProfiles::handleProfile(Item *profileItem)
{
QVariantMap values;
evaluateProfileValues(QualifiedId(), profileItem, profileItem, values);
@@ -93,15 +85,11 @@ void LocalProfiles::Private::handleProfile(Item *profileItem)
throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.")
.arg(profileName), profileItem->location());
}
- if (profiles.contains(profileName)) {
- throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName),
- profileItem->location());
- }
- profiles.insert(profileName, values);
+ m_loaderState.topLevelProject().addLocalProfile(profileName, values, profileItem->location());
}
-void LocalProfiles::Private::evaluateProfileValues(const QualifiedId &namePrefix, Item *item,
- Item *profileItem, QVariantMap &values)
+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) {
@@ -119,16 +107,16 @@ void LocalProfiles::Private::evaluateProfileValues(const QualifiedId &namePrefix
case Value::JSSourceValueType:
if (item != profileItem)
item->setScope(profileItem);
- const ScopedJsValue sv(loaderState.evaluator().engine()->context(),
- loaderState.evaluator().value(item, it.key()));
+ const ScopedJsValue sv(m_loaderState.evaluator().engine()->context(),
+ m_loaderState.evaluator().value(item, it.key()));
values.insert(name.join(QLatin1Char('.')),
- getJsVariant(loaderState.evaluator().engine()->context(), sv));
+ getJsVariant(m_loaderState.evaluator().engine()->context(), sv));
break;
}
}
}
-void LocalProfiles::Private::collectProfiles(Item *productOrProject, Item *projectScope)
+void LocalProfiles::collectProfiles(Item *productOrProject, Item *projectScope)
{
Item * scope = productOrProject->type() == ItemType::Project ? projectScope : nullptr;
for (auto it = productOrProject->children().begin();
@@ -137,7 +125,7 @@ void LocalProfiles::Private::collectProfiles(Item *productOrProject, Item *proje
if (childItem->type() == ItemType::Profile) {
if (!scope) {
const ItemValuePtr itemValue = ItemValue::create(productOrProject);
- scope = Item::create(productOrProject->pool(), ItemType::Scope);
+ scope = Item::create(&m_loaderState.itemPool(), ItemType::Scope);
scope->setProperty(StringConstants::productVar(), itemValue);
scope->setFile(productOrProject->file());
scope->setScope(projectScope);
@@ -146,7 +134,7 @@ void LocalProfiles::Private::collectProfiles(Item *productOrProject, Item *proje
try {
handleProfile(childItem);
} catch (const ErrorInfo &e) {
- handlePropertyError(e, loaderState.parameters(), loaderState.logger());
+ handlePropertyError(e, m_loaderState.parameters(), m_loaderState.logger());
}
it = productOrProject->children().erase(it); // TODO: delete item and scope
} else {
diff --git a/src/lib/corelib/loader/localprofiles.h b/src/lib/corelib/loader/localprofiles.h
index e31c8c81b..7d9eee88e 100644
--- a/src/lib/corelib/loader/localprofiles.h
+++ b/src/lib/corelib/loader/localprofiles.h
@@ -39,27 +39,11 @@
#pragma once
-#include <tools/pimpl.h>
-#include <QVariantMap>
-
namespace qbs::Internal {
class Item;
class LoaderState;
-// This class evaluates all Profile items encountered in the project tree and holds the results.
-class LocalProfiles
-{
-public:
- LocalProfiles(LoaderState &loaderState);
- ~LocalProfiles();
-
- void collectProfilesFromItems(Item *productOrProject, Item *projectScope);
- const QVariantMap &profiles() const;
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+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
index 73b676be4..1c0359217 100644
--- a/src/lib/corelib/loader/moduleinstantiator.cpp
+++ b/src/lib/corelib/loader/moduleinstantiator.cpp
@@ -55,39 +55,39 @@
#include <utility>
namespace qbs::Internal {
-class ModuleInstantiator::Private
+
+static std::pair<const Item *, Item *>
+getOrSetModuleInstanceItem(Item *container, Item *moduleItem, const QualifiedId &moduleName,
+ const QString &id, bool replace, LoaderState &loaderState);
+
+class ModuleInstantiator
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
+ ModuleInstantiator(const InstantiationContext &context, LoaderState &loaderState)
+ : context(context), loaderState(loaderState) {}
+
+ void instantiate();
- void overrideProperties(const Context &context);
- void setupScope(const Context &context);
- void exchangePlaceholderItem(Item *product, Item *loadingItem, const QString &loadingName,
- Item *moduleItemForItemValues, const QualifiedId &moduleName, const QString &id,
- bool isProductDependency, bool alreadyLoaded);
- std::pair<const Item *, Item *>
- getOrSetModuleInstanceItem(Item *container, Item *moduleItem, const QualifiedId &moduleName,
- const QString &id, bool replace);
+private:
+ void overrideProperties();
+ void setupScope();
+ void exchangePlaceholderItem(Item *loadingItem, Item *moduleItemForItemValues);
+ const InstantiationContext &context;
LoaderState &loaderState;
- qint64 elapsedTime = 0;
};
-ModuleInstantiator::ModuleInstantiator(LoaderState &loaderState)
- : d(makePimpl<Private>(loaderState)) {}
-ModuleInstantiator::~ModuleInstantiator() = default;
-
-void ModuleInstantiator::instantiate(const Context &context)
+void ModuleInstantiator::instantiate()
{
- AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTime : nullptr);
+ 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);
- d->overrideProperties(context);
- d->setupScope(context);
+ overrideProperties();
+ setupScope();
}
// This strange-looking code deals with the fact that our syntax cannot properly handle
@@ -109,37 +109,42 @@ void ModuleInstantiator::instantiate(const Context &context)
// }
// 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.
- d->exchangePlaceholderItem(
- context.product, context.loadingItem, context.loadingName, moduleItemForItemValues,
- context.moduleName, context.id, context.exportingProduct, context.alreadyLoaded);
-
- if (!context.alreadyLoaded && context.product && context.product != context.loadingItem) {
- d->exchangePlaceholderItem(
- context.product, context.product, context.productName, moduleItemForItemValues,
- context.moduleName, context.id, context.exportingProduct, context.alreadyLoaded);
+ exchangePlaceholderItem(context.loadingItem, moduleItemForItemValues);
+
+ if (!context.alreadyLoaded && context.product.item
+ && context.product.item != context.loadingItem) {
+ exchangePlaceholderItem(context.product.item, moduleItemForItemValues);
}
}
-void ModuleInstantiator::Private::exchangePlaceholderItem(
- Item *product, Item *loadingItem, const QString &loadingName, Item *moduleItemForItemValues,
- const QualifiedId &moduleName, const QString &id, bool isProductModule, bool alreadyLoaded)
+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, moduleName, id, true);
+ 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(), moduleName.toString(),
+ 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:
@@ -148,7 +153,7 @@ void ModuleInstantiator::Private::exchangePlaceholderItem(
// (see getOrSetModuleInstanceItem() below for details).
if (oldItem == newItem) {
QBS_CHECK(oldItem->type() == ItemType::ModuleInstance);
- QBS_CHECK(alreadyLoaded || isProductModule);
+ QBS_CHECK(context.alreadyLoaded || context.exportingProduct);
return;
}
@@ -172,22 +177,96 @@ void ModuleInstantiator::Private::exchangePlaceholderItem(
}
// Now merge the locally attached values into the actual module instance.
- loaderState.propertyMerger().mergeFromLocalInstance(product, loadingItem, loadingName,
- oldItem, moduleItemForItemValues);
+ 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 *ModuleInstantiator::retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name)
+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 &parameters = 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()
{
- return d->getOrSetModuleInstanceItem(containerItem, nullptr, name, {}, false).second;
+ 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()));
+ }
}
-Item *ModuleInstantiator::retrieveQbsItem(Item *containerItem)
+void instantiateModule(const InstantiationContext &context, LoaderState &loaderState)
{
- return retrieveModuleInstanceItem(containerItem, StringConstants::qbsModule());
+ ModuleInstantiator(context, loaderState).instantiate();
}
// This important function deals with retrieving and setting (pseudo-)module instances from/on
@@ -220,9 +299,9 @@ Item *ModuleInstantiator::retrieveQbsItem(Item *containerItem)
// 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 *> ModuleInstantiator::Private::getOrSetModuleInstanceItem(
+std::pair<const Item *, Item *> getOrSetModuleInstanceItem(
Item *container, Item *moduleItem, const QualifiedId &moduleName, const QString &id,
- bool replace)
+ bool replace, LoaderState &loaderState)
{
Item *instance = container;
const QualifiedId itemValueName
@@ -248,9 +327,9 @@ std::pair<const Item *, Item *> ModuleInstantiator::Private::getOrSetModuleInsta
Item *newItem = i < itemValueName.size() - 1
? Item::create(&loaderState.itemPool(), ItemType::ModulePrefix)
: moduleItem
- ? moduleItem
- : Item::create(&loaderState.itemPool(),
- ItemType::ModuleInstancePlaceholder);
+ ? moduleItem
+ : Item::create(&loaderState.itemPool(),
+ ItemType::ModuleInstancePlaceholder);
instance->setProperty(moduleNameSegment, ItemValue::create(newItem));
instance = newItem;
}
@@ -258,82 +337,4 @@ std::pair<const Item *, Item *> ModuleInstantiator::Private::getOrSetModuleInsta
return {nullptr, instance};
}
-void ModuleInstantiator::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << QByteArray(indent, ' ')
- << Tr::tr("Instantiating modules took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
-}
-
-void ModuleInstantiator::Private::overrideProperties(const ModuleInstantiator::Context &context)
-{
- // 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.productName + QLatin1Char('.') + fullName;
- const SetupProjectParameters &parameters = 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::Private::setupScope(const ModuleInstantiator::Context &context)
-{
- Item * const scope = Item::create(&loaderState.itemPool(), ItemType::Scope);
- QBS_CHECK(context.module->file());
- scope->setFile(context.module->file());
- QBS_CHECK(context.projectScope);
- context.projectScope->copyProperty(StringConstants::projectVar(), scope);
- if (context.productScope)
- context.productScope->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);
- 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()));
- }
-}
-
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/moduleinstantiator.h b/src/lib/corelib/loader/moduleinstantiator.h
index 7be886869..5e9ad2f44 100644
--- a/src/lib/corelib/loader/moduleinstantiator.h
+++ b/src/lib/corelib/loader/moduleinstantiator.h
@@ -39,7 +39,6 @@
#pragma once
-#include <tools/pimpl.h>
#include <QtGlobal>
QT_BEGIN_NAMESPACE
@@ -49,48 +48,35 @@ QT_END_NAMESPACE
namespace qbs::Internal {
class Item;
class LoaderState;
+class ProductContext;
class QualifiedId;
-// This class is responsible for setting up a proper module instance from a bunch of items:
+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.
-// In addition, it also provides helper functions for retrieving/setting module instance items
-// for special purposes.
-class ModuleInstantiator
-{
-public:
- ModuleInstantiator(LoaderState &loaderState);
- ~ModuleInstantiator();
+void instantiateModule(const InstantiationContext &context, LoaderState &loaderState);
- struct Context {
- Item * const product;
- const QString &productName;
- Item * const loadingItem;
- const QString &loadingName;
- Item * const module;
- Item * const moduleWithSameName;
- Item * const exportingProduct;
- Item * const productScope;
- Item * const projectScope;
- const QualifiedId &moduleName;
- const QString &id;
- const bool alreadyLoaded;
- };
- void instantiate(const Context &context);
-
- // Note that these will also create the respective item value if it does not exist yet.
- Item *retrieveModuleInstanceItem(Item *containerItem, const QualifiedId &name);
- Item *retrieveQbsItem(Item *containerItem);
-
- void printProfilingInfo(int indent);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+// 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
index 98548278f..d26849655 100644
--- a/src/lib/corelib/loader/moduleloader.cpp
+++ b/src/lib/corelib/loader/moduleloader.cpp
@@ -42,13 +42,14 @@
#include "itemreader.h"
#include "loaderutils.h"
#include "moduleproviderloader.h"
-#include "productitemmultiplexer.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>
@@ -64,42 +65,37 @@
namespace qbs::Internal {
-class ModuleLoader::Private
+class ModuleLoader
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
-
- std::pair<Item *, bool> loadModuleFile(const ProductContext &product,
- const QString &moduleName, const QString &filePath);
- std::pair<Item *, bool> getModulePrototype(const ModuleLoader::ProductContext &product,
- const QString &moduleName, const QString &filePath);
- bool evaluateModuleCondition(const ModuleLoader::ProductContext &product, Item *module,
- const QString &fullModuleName);
- void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
- const Item::Modules &modules);
-
- LoaderState &loaderState;
- ModuleProviderLoader providerLoader{loaderState};
-
- // 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>>> modulePrototypes;
-
- // The keys are module prototypes and products, the values specify whether the module's
- // condition is true for that product.
- std::unordered_map<std::pair<Item *, const Item *>, bool> modulePrototypeEnabledInfo;
-
- std::unordered_map<const Item *, std::vector<ErrorInfo>> unknownProfilePropertyErrors;
- std::unordered_map<const Item *, Item::PropertyDeclarationMap> parameterDeclarations;
- std::unordered_map<const Item *, std::optional<QVariantMap>> providerConfigsPerProduct;
- QHash<std::pair<QString, QualifiedId>, std::optional<QString>> existingModulePathCache;
- std::map<QString, QStringList> moduleDirListCache;
-
- qint64 elapsedTimeModuleProviders = 0;
-};
+ ModuleLoader(
+ LoaderState &loaderState,
+ ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName)
+ : m_loaderState(loaderState)
+ , m_product(product)
+ , m_dependsItemLocation(dependsItemLocation)
+ , m_moduleName(moduleName)
+ {}
-ModuleLoader::ModuleLoader(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) { }
+ Item *load();
-ModuleLoader::~ModuleLoader() = default;
+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
{
@@ -148,180 +144,129 @@ static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidate
return maxIt->item;
}
-ModuleLoader::Result ModuleLoader::searchAndLoadModuleFile(
- const ProductContext &productContext, const CodeLocation &dependsItemLocation,
- const QualifiedId &moduleName, FallbackMode fallbackMode, bool isRequired)
+Item *searchAndLoadModuleFile(
+ LoaderState &loaderState,
+ ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName)
{
- const auto findExistingModulePath = [&](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
- auto &moduleInfo = d->existingModulePathCache[{searchPath, moduleName}];
- if (moduleInfo)
- return *moduleInfo;
-
- QString dirPath = searchPath + QStringLiteral("/modules");
- for (const QString &moduleNamePart : moduleName) {
- dirPath = FileInfo::resolvePath(dirPath, moduleNamePart);
- if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) {
- return *(moduleInfo = QString());
- }
- }
-
- return *(moduleInfo = dirPath);
- };
- const auto findExistingModulePaths = [&] {
- const QStringList &searchPaths = d->loaderState.itemReader().allSearchPaths();
- QStringList result;
- result.reserve(searchPaths.size());
- for (const auto &path: searchPaths) {
- const QString dirPath = findExistingModulePath(path);
- if (!dirPath.isEmpty())
- result.append(dirPath);
- }
- return result;
- };
+ return ModuleLoader(loaderState, product, dependsItemLocation, moduleName).load();
+}
- SearchPathsManager searchPathsManager(d->loaderState.itemReader());
+Item *ModuleLoader::load()
+{
+ SearchPathsManager searchPathsManager(m_loaderState.itemReader());
- Result loadResult;
- auto existingPaths = findExistingModulePaths();
+ QStringList existingPaths = findModuleDirectories();
if (existingPaths.isEmpty()) { // no suitable names found, try to use providers
- AccumulatingTimer providersTimer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTimeModuleProviders : nullptr);
- std::optional<QVariantMap> &providerConfig
- = d->providerConfigsPerProduct[productContext.productItem];
- auto result = d->providerLoader.executeModuleProviders(
- {productContext.productItem, productContext.projectItem, productContext.name,
- productContext.uniqueName, productContext.moduleProperties, providerConfig},
- dependsItemLocation,
- moduleName,
- fallbackMode);
- loadResult.providerProbes << result.probes;
- if (!providerConfig)
- providerConfig = result.providerConfig;
+ 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" << moduleName.toString()
+ qCDebug(lcModuleLoader) << "Re-checking for module" << m_moduleName.toString()
<< "with newly added search paths from module provider";
- d->loaderState.itemReader().pushExtraSearchPaths(*result.searchPaths);
- existingPaths = findExistingModulePaths();
+ m_loaderState.itemReader().pushExtraSearchPaths(*result.searchPaths);
+ existingPaths = findModuleDirectories();
}
}
- const auto getModuleFileNames = [&](const QString &dirPath) -> QStringList & {
- QStringList &moduleFileNames = d->moduleDirListCache[dirPath];
- if (moduleFileNames.empty()) {
- QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards());
- while (dirIter.hasNext())
- moduleFileNames += dirIter.next();
- }
- return moduleFileNames;
- };
-
- const QString fullName = moduleName.toString();
+ 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 QString &dirPath = existingPaths.at(i);
- QStringList &moduleFileNames = getModuleFileNames(dirPath);
- for (auto it = moduleFileNames.begin(); it != moduleFileNames.end(); ) {
- const QString &filePath = *it;
- const auto [module, triedToLoad] = d->loadModuleFile(productContext, fullName,
- filePath);
+ 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)
- it = moduleFileNames.erase(it);
- else
- ++it;
+ m_loaderState.topLevelProject().removeModuleFileFromDirectoryCache(filePath);
triedToLoadModule = triedToLoadModule || triedToLoad;
}
}
- if (candidates.empty()) {
- if (!isRequired) {
- loadResult.moduleItem = createNonPresentModule(
- *productContext.projectItem->pool(), fullName, QStringLiteral("not found"),
- nullptr);
- return loadResult;
- }
- if (Q_UNLIKELY(triedToLoadModule)) {
- throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName),
- dependsItemLocation);
- }
- return loadResult;
- }
+ if (candidates.empty())
+ return nullptr;
+ Item *moduleItem = nullptr;
if (candidates.size() == 1) {
- loadResult.moduleItem = candidates.at(0).item;
+ moduleItem = candidates.at(0).item;
} else {
for (auto &candidate : candidates) {
- candidate.priority = d->loaderState.evaluator()
+ ModuleItemLocker lock(*candidate.item);
+ candidate.priority = m_loaderState.evaluator()
.intValue(candidate.item, StringConstants::priorityProperty(),
candidate.priority);
}
- loadResult.moduleItem = chooseModuleCandidate(candidates, fullName);
- }
-
- const QString fullProductName = ProductItemMultiplexer::fullProductDisplayName(
- productContext.name, productContext.multiplexId);
- const auto it = d->unknownProfilePropertyErrors.find(loadResult.moduleItem);
- if (it != d->unknownProfilePropertyErrors.cend()) {
- ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values "
- "in profile '%3':")
- .arg(moduleName.toString(), fullProductName, productContext.profile));
- for (const ErrorInfo &e : it->second)
- error.append(e.toString());
- handlePropertyError(error, d->loaderState.parameters(), d->loaderState.logger());
+ moduleItem = chooseModuleCandidate(candidates, fullName);
}
- return loadResult;
-}
-
-void ModuleLoader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo)
-{
- d->providerLoader.setStoredModuleProviderInfo(moduleProviderInfo);
-}
-
-StoredModuleProviderInfo ModuleLoader::storedModuleProviderInfo() const
-{
- return d->providerLoader.storedModuleProviderInfo();
-}
-
-const Set<QString> &ModuleLoader::tempQbsFiles() const
-{
- return d->providerLoader.tempQbsFiles();
+ checkForUnknownProfileProperties(moduleItem);
+ return moduleItem;
}
-std::pair<Item *, bool> ModuleLoader::Private::loadModuleFile(
- const ProductContext &product, const QString &moduleName, const QString &filePath)
+std::pair<Item *, bool> ModuleLoader::loadModuleFile(const QString &moduleName,
+ const QString &filePath)
{
qCDebug(lcModuleLoader) << "loadModuleFile" << moduleName << "from" << filePath;
- const auto [module, triedToLoad] = getModulePrototype(product, moduleName, filePath);
+ Item * const module = getModulePrototype(moduleName, filePath);
if (!module)
- return {nullptr, triedToLoad};
+ return {nullptr, false};
- const auto key = std::make_pair(module, product.productItem);
- const auto it = modulePrototypeEnabledInfo.find(key);
- if (it != modulePrototypeEnabledInfo.end()) {
+ 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, triedToLoad};
+ return {it->second ? module : nullptr, true};
}
- if (!evaluateModuleCondition(product, module, moduleName)) {
+ if (!evaluateModuleCondition(module, moduleName)) {
qCDebug(lcModuleLoader) << "condition of module" << moduleName << "is false";
- modulePrototypeEnabledInfo.insert({key, false});
- return {nullptr, triedToLoad};
+ 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(loaderState.parameters().libexecPath()));
+ VariantValue::create(m_loaderState.parameters().libexecPath()));
const Version qbsVersion = LanguageInfo::qbsVersion();
module->setProperty(QStringLiteral("versionMajor"),
@@ -334,47 +279,29 @@ std::pair<Item *, bool> ModuleLoader::Private::loadModuleFile(
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());
- }
- parameterDeclarations.insert({module, decls});
- }
-
- modulePrototypeEnabledInfo.insert({key, true});
- return {module, triedToLoad};
-}
-
-std::pair<Item *, bool> ModuleLoader::Private::getModulePrototype(
- const ProductContext &product, const QString &moduleName, const QString &filePath)
-{
- auto &prototypeList = modulePrototypes[filePath];
- for (const auto &prototype : prototypeList) {
- if (prototype.second == product.profile) {
- qCDebug(lcModuleLoader) << "prototype cache hit (level 1)";
- return {prototype.first, true};
+ if (param->type() == ItemType::Parameter) {
+ const auto &paramDecls = 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);
}
- Item * const module = loaderState.itemReader().setupItemFromFile(filePath, CodeLocation());
- 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, false};
- }
- prototypeList.emplace_back(module, product.profile);
-
// 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
- = product.profileModuleProperties.value(moduleName).toMap();
+ = m_product.profileModuleProperties.value(moduleName).toMap();
for (auto it = profileModuleProperties.cbegin(); it != profileModuleProperties.cend(); ++it) {
if (Q_UNLIKELY(!module->hasProperty(it.key()))) {
- unknownProfilePropertyErrors[module].emplace_back(Tr::tr("Unknown property: %1.%2")
- .arg(moduleName, 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());
@@ -385,14 +312,12 @@ std::pair<Item *, bool> ModuleLoader::Private::getModulePrototype(
module->setProperty(it.key(), v);
}
- return {module, true};
+ return module;
}
-bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &product,
- Item *module, const QString &fullModuleName)
+bool ModuleLoader::evaluateModuleCondition(Item *module, const QString &fullModuleName)
{
- // Evaluator reqires module name to be set.
- module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName));
+ ModuleItemLocker locker(*module);
// Temporarily make the product's qbs module instance available, so the condition
// can use qbs.targetOS etc.
@@ -405,7 +330,7 @@ bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &produc
if (m_needsQbsItem) {
m_prevQbsItemValue = module->property(StringConstants::qbsModule());
module->setProperty(StringConstants::qbsModule(),
- product.productItem->property(StringConstants::qbsModule()));
+ product.item->property(StringConstants::qbsModule()));
}
}
~TempQbsModuleProvider()
@@ -423,114 +348,63 @@ bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &produc
const bool m_needsQbsItem;
};
- const TempQbsModuleProvider tempQbs(product, module, fullModuleName);
- return loaderState.evaluator().boolValue(module, StringConstants::conditionProperty());
+ const TempQbsModuleProvider tempQbs(m_product, module, fullModuleName);
+ return m_loaderState.evaluator().boolValue(module, StringConstants::conditionProperty());
}
-class DependencyParameterDeclarationCheck
+void ModuleLoader::checkForUnknownProfileProperties(const Item *module)
{
-public:
- DependencyParameterDeclarationCheck(
- const QString &productName, const Item *productItem,
- const std::unordered_map<const Item *, Item::PropertyDeclarationMap> &decls)
- : m_productName(productName), m_productItem(productItem), m_parameterDeclarations(decls)
- {}
-
- void operator()(const QVariantMap &parameters) const { check(parameters, QualifiedId()); }
-
-private:
- void check(const QVariantMap &parameters, 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_parameterDeclarations.find(m->item->rootPrototype());
- if (decls == m_parameterDeclarations.end() || !decls->second.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 std::unordered_map<const Item *, Item::PropertyDeclarationMap> &m_parameterDeclarations;
-};
+ const std::vector<ErrorInfo> &errors
+ = m_loaderState.topLevelProject().unknownProfilePropertyErrors(module);
+ if (errors.empty())
+ return;
-void ModuleLoader::checkDependencyParameterDeclarations(const Item *productItem,
- const QString &productName) const
-{
- DependencyParameterDeclarationCheck dpdc(productName, productItem, d->parameterDeclarations);
- for (const Item::Module &dep : productItem->modules()) {
- if (!dep.parameters.empty())
- dpdc(dep.parameters);
- }
+ 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());
}
-void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem,
- const Item::Modules &modules)
+QString ModuleLoader::findModuleDirectory(const QString &searchPath)
{
- for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) {
- if (it.value()->type() != Value::ItemValueType)
- continue;
- d->forwardParameterDeclarations(it.key(),
- std::static_pointer_cast<ItemValue>(it.value())->item(),
- modules);
- }
+ // 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;
+ });
}
-void ModuleLoader::Private::forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
- const Item::Modules &modules)
+QStringList ModuleLoader::findModuleDirectories()
{
- auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) {
- return m.name == moduleName;
- });
- if (it != modules.end()) {
- item->setPropertyDeclarations(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);
- }
+ 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;
}
-void ModuleLoader::printProfilingInfo(int indent)
+QStringList ModuleLoader::getModuleFilePaths(const QString &dir)
{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << QByteArray(indent, ' ')
- << Tr::tr("Running module providers took %1.")
- .arg(elapsedTimeString(d->elapsedTimeModuleProviders));
+ 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/loader/moduleloader.h b/src/lib/corelib/loader/moduleloader.h
index af5a8c0e9..b07f01836 100644
--- a/src/lib/corelib/loader/moduleloader.h
+++ b/src/lib/corelib/loader/moduleloader.h
@@ -39,62 +39,19 @@
#pragma once
-
-#include <language/forward_decls.h>
-#include <language/item.h>
-#include <tools/pimpl.h>
-#include <tools/set.h>
-
-#include <QString>
-#include <QVariantMap>
-
-#include <vector>
-
namespace qbs {
class CodeLocation;
namespace Internal {
-enum class FallbackMode;
+class Item;
class LoaderState;
-class StoredModuleProviderInfo;
-
-class ModuleLoader
-{
-public:
- ModuleLoader(LoaderState &loaderState);
- ~ModuleLoader();
-
- struct ProductContext {
- Item * const productItem;
- const Item * const projectItem;
- const QString &name;
- const QString &uniqueName;
- const QString &profile;
- const QString &multiplexId;
- const QVariantMap &moduleProperties;
- const QVariantMap &profileModuleProperties;
- };
- struct Result {
- Item *moduleItem = nullptr;
- std::vector<ProbeConstPtr> providerProbes;
- };
- Result searchAndLoadModuleFile(const ProductContext &productContext,
- const CodeLocation &dependsItemLocation,
- const QualifiedId &moduleName,
- FallbackMode fallbackMode, bool isRequired);
-
- void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
- StoredModuleProviderInfo storedModuleProviderInfo() const;
- const Set<QString> &tempQbsFiles() const;
-
- void checkDependencyParameterDeclarations(const Item *productItem,
- const QString &productName) const;
- void forwardParameterDeclarations(const Item *dependsItem, const Item::Modules &modules);
- void printProfilingInfo(int indent);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+class ProductContext;
+class QualifiedId;
+
+Item *searchAndLoadModuleFile(
+ LoaderState &loaderState,
+ ProductContext &product,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName);
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/loader/modulepropertymerger.cpp b/src/lib/corelib/loader/modulepropertymerger.cpp
index 5ac89d776..c9dd56ccf 100644
--- a/src/lib/corelib/loader/modulepropertymerger.cpp
+++ b/src/lib/corelib/loader/modulepropertymerger.cpp
@@ -49,89 +49,101 @@
#include <tools/set.h>
#include <tools/setupprojectparameters.h>
+#include <unordered_set>
+
namespace qbs::Internal {
-class ModulePropertyMerger::Private
+class ModulePropertyMerger
{
public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
-
- int compareValuePriorities(const Item *productItem, const ValueConstPtr &v1,
- const ValueConstPtr &v2);
- ValuePtr mergeListValues(const Item *productItem, const ValuePtr &currentHead, const ValuePtr &newElem);
- void mergePropertyFromLocalInstance(const Item *productItem, Item *loadingItem,
- const QString &loadingName, Item *globalInstance,
- const QString &name, const ValuePtr &value);
- bool doFinalMerge(const Item *productItem, Item *moduleItem);
- bool doFinalMerge(const Item *productItem, const PropertyDeclaration &propertyDecl,
- ValuePtr &propertyValue);
-
- LoaderState &loaderState;
- qint64 elapsedTime = 0;
+ 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 &currentHead, 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 ModulePropertyMerger::mergeFromLocalInstance(
- const Item *productItem, Item *loadingItem, const QString &loadingName,
- const Item *localInstance, Item *globalInstance)
+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(d->loaderState.parameters().logElapsedTime() ? &d->elapsedTime : nullptr);
+ AccumulatingTimer t(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.propertyMerging : nullptr);
for (auto it = localInstance->properties().constBegin();
it != localInstance->properties().constEnd(); ++it) {
- d->mergePropertyFromLocalInstance(productItem, loadingItem, loadingName,
- globalInstance, it.key(), it.value());
+ mergePropertyFromLocalInstance(loadingItem, loadingName, globalInstance, it.key(),
+ it.value());
}
}
-void ModulePropertyMerger::doFinalMerge(const Item *productItem)
+void ModulePropertyMerger::doFinalMerge()
{
- AccumulatingTimer t(d->loaderState.parameters().logElapsedTime() ? &d->elapsedTime : nullptr);
+ AccumulatingTimer t(m_loaderState.parameters().logElapsedTime()
+ ? &m_product.timingData.propertyMerging : nullptr);
- Set<const Item *> itemsToInvalidate;
- for (const Item::Module &module : productItem->modules()) {
- if (d->doFinalMerge(productItem, module.item))
- itemsToInvalidate << module.item;
+ std::unordered_set<const Item *> itemsToInvalidate;
+ for (const Item::Module &module : m_product.item->modules()) {
+ if (doFinalMerge(module.item))
+ itemsToInvalidate.insert(module.item);
}
- const auto collectDependentItems = [&itemsToInvalidate](const Item *item,
- const auto &collect) -> bool {
- const bool alreadyInSet = itemsToInvalidate.contains(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 << item;
+ itemsToInvalidate.insert(item);
return addItem || alreadyInSet;
};
- collectDependentItems(productItem, collectDependentItems);
+ collectDependentItems(m_product.item, collectDependentItems);
for (const Item * const item : itemsToInvalidate)
- d->loaderState.evaluator().clearCache(item);
+ m_loaderState.evaluator().clearCache(item);
}
-void ModulePropertyMerger::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << QByteArray(indent, ' ')
- << Tr::tr("Merging module property values took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
-}
-
-ModulePropertyMerger::ModulePropertyMerger(LoaderState &loaderState)
- : d(makePimpl<Private>(loaderState)) { }
-ModulePropertyMerger::~ModulePropertyMerger() = default;
-
-int ModulePropertyMerger::Private::compareValuePriorities(
- const Item *productItem, const ValueConstPtr &v1, const ValueConstPtr &v2)
+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(productItem);
- const int prio2 = v2->priority(productItem);
+ 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
@@ -139,32 +151,36 @@ int ModulePropertyMerger::Private::compareValuePriorities(
return prioDiff;
}
-ValuePtr ModulePropertyMerger::Private::mergeListValues(
- const Item *productItem, const ValuePtr &currentHead, const ValuePtr &newElem)
+ValuePtr ModulePropertyMerger::mergeListValues(const ValuePtr &currentHead, const ValuePtr &newElem)
{
QBS_CHECK(newElem);
QBS_CHECK(!newElem->next());
if (!currentHead)
- return !newElem->expired(productItem) ? newElem : newElem->next();
+ return !newElem->expired(m_product.item) ? newElem : newElem->next();
- QBS_CHECK(!currentHead->expired(productItem));
+ QBS_CHECK(!currentHead->expired(m_product.item));
- if (newElem->expired(productItem))
+ if (newElem->expired(m_product.item))
return currentHead;
- if (compareValuePriorities(productItem, currentHead, newElem) < 0) {
+ if (compareValuePriorities(currentHead, newElem) < 0) {
newElem->setNext(currentHead);
return newElem;
}
- currentHead->setNext(mergeListValues(productItem, currentHead->next(), newElem));
+ currentHead->setNext(mergeListValues(currentHead->next(), newElem));
return currentHead;
}
-void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
- const Item *productItem, Item *loadingItem, const QString &loadingName, Item *globalInstance,
+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())
@@ -173,9 +189,9 @@ void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
.arg(name), value->location());
}
if (const ErrorInfo error = decl.checkForDeprecation(
- loaderState.parameters().deprecationWarningMode(), value->location(),
- loaderState.logger()); error.hasError()) {
- handlePropertyError(error, loaderState.parameters(), loaderState.logger());
+ 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.
@@ -196,9 +212,9 @@ void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
QBS_CHECK(value->type() == Value::JSSourceValueType);
if (decl.isScalar()) {
- QBS_CHECK(!globalVal->expired(productItem));
- QBS_CHECK(!value->expired(productItem));
- if (compareValuePriorities(productItem, globalVal, value) < 0) {
+ 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);
@@ -207,27 +223,27 @@ void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
globalVal->addCandidate(value);
}
} else {
- if (const ValuePtr &newChainStart = mergeListValues(productItem, globalVal, value);
+ if (const ValuePtr &newChainStart = mergeListValues(globalVal, value);
newChainStart != globalVal) {
globalInstance->setProperty(decl.name(), newChainStart);
}
}
}
-bool ModulePropertyMerger::Private::doFinalMerge(const Item *productItem, Item *moduleItem)
+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(productItem, moduleItem->propertyDeclaration(it.key()), it.value()))
+ if (doFinalMerge(moduleItem->propertyDeclaration(it.key()), it.value()))
mustInvalidateCache = true;
}
return mustInvalidateCache;
}
-bool ModulePropertyMerger::Private::doFinalMerge(
- const Item *productItem, const PropertyDeclaration &propertyDecl, ValuePtr &propertyValue)
+bool ModulePropertyMerger::doFinalMerge(const PropertyDeclaration &propertyDecl,
+ ValuePtr &propertyValue)
{
if (propertyValue->type() == Value::VariantValueType) {
QBS_CHECK(!propertyValue->next());
@@ -240,10 +256,10 @@ bool ModulePropertyMerger::Private::doFinalMerge(
if (propertyValue->candidates().empty())
return false;
std::pair<int, std::vector<ValuePtr>> candidatesWithHighestPrio;
- candidatesWithHighestPrio.first = propertyValue->priority(productItem);
+ candidatesWithHighestPrio.first = propertyValue->priority(m_product.item);
candidatesWithHighestPrio.second.push_back(propertyValue);
for (const ValuePtr &v : propertyValue->candidates()) {
- const int prio = v->priority(productItem);
+ const int prio = v->priority(m_product.item);
if (prio < candidatesWithHighestPrio.first)
continue;
if (prio > candidatesWithHighestPrio.first) {
@@ -271,11 +287,16 @@ bool ModulePropertyMerger::Private::doFinalMerge(
error.append({}, v->location());
}
if (error.items().size() > 2)
- loaderState.logger().printWarning(error);
+ 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;
}
@@ -291,7 +312,7 @@ bool ModulePropertyMerger::Private::doFinalMerge(
}
ValuePtr newValue;
for (const ValuePtr &v : singleValuesBefore)
- newValue = mergeListValues(productItem, newValue, v);
+ newValue = mergeListValues(newValue, v);
std::vector<ValuePtr> singleValuesAfter;
for (ValuePtr current = propertyValue; current; current = current->next())
singleValuesAfter.push_back(current);
diff --git a/src/lib/corelib/loader/modulepropertymerger.h b/src/lib/corelib/loader/modulepropertymerger.h
index 4ea72c2a4..a6643216f 100644
--- a/src/lib/corelib/loader/modulepropertymerger.h
+++ b/src/lib/corelib/loader/modulepropertymerger.h
@@ -39,15 +39,18 @@
#pragma once
-#include <tools/pimpl.h>
-
#include <QtGlobal>
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
namespace qbs::Internal {
class Item;
class LoaderState;
+class ProductContext;
-// This class comprises functions for collecting values attached to module properties
+// 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"
@@ -62,35 +65,23 @@ class LoaderState;
// 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.
-class ModulePropertyMerger
-{
-public:
- ModulePropertyMerger(LoaderState &loaderState);
- ~ModulePropertyMerger();
-
- // 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(const Item *productItem, Item *loadingItem,
- const QString &loadingName, const Item *localInstance,
- Item *globalInstance);
-
- // 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(const Item *productItem);
- void printProfilingInfo(int indent);
+// 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);
-private:
- class Private;
- Pimpl<Private> d;
-};
+// 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
index 06123597a..d0789facd 100644
--- a/src/lib/corelib/loader/moduleproviderloader.cpp
+++ b/src/lib/corelib/loader/moduleproviderloader.cpp
@@ -57,80 +57,71 @@
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
-#include <QtCore/qtemporaryfile.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(
- const ProductContext &productContext,
- const CodeLocation &dependsItemLocation,
- const QualifiedId &moduleName,
- FallbackMode fallbackMode)
+ ProductContext &productContext,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName)
{
ModuleProviderLoader::ModuleProviderResult result;
- std::vector<Provider> providersToRun;
- qCDebug(lcModuleLoader) << "Module" << moduleName.toString()
- << "not found, checking for module providers";
- const auto providerNames = getModuleProviders(productContext.productItem);
- 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});
+ 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, providersToRun);
-
- if (fallbackMode == FallbackMode::Enabled
- && !result.providerFound
- && !providerNames) {
- qCDebug(lcModuleLoader) << "Specific module provider not found for"
- << moduleName.toString() << ", setting up fallback.";
result = executeModuleProvidersHelper(
- productContext,
- dependsItemLocation,
- {{moduleName, ModuleProviderLookup::Fallback}});
+ 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(
- const ProductContext &product,
+ ProductContext &product,
const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
const std::vector<Provider> &providers)
{
if (providers.empty())
return {};
QStringList allSearchPaths;
ModuleProviderResult result;
- result.providerConfig = product.providerConfig ? *product.providerConfig
- : getModuleProviderConfig(product);
+ setupModuleProviderConfig(product);
const auto qbsModule = evaluateQbsModule(product);
for (const auto &[name, lookupType] : providers) {
- const QVariantMap config = result.providerConfig.value(name.toString()).toMap();
- ModuleProviderInfo &info = m_storedModuleProviderInfo.providers[
- {name.toString(), config, qbsModule, int(lookupType)}];
- const bool fromCache = !info.name.isEmpty();
- if (!fromCache) {
- info.name = name;
- info.config = config;
- info.providerFile = findModuleProviderFile(name, lookupType);
- if (!info.providerFile.isEmpty()) {
- qCDebug(lcModuleLoader) << "Running provider" << name << "at" << info.providerFile;
- const auto evalResult = evaluateModuleProvider(
- product, dependsItemLocation, name, info.providerFile, config, qbsModule);
- info.searchPaths = evalResult.first;
- result.probes << evalResult.second;
- info.transientOutput = m_loaderState.parameters().dryRun();
- }
- }
+ 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()));
@@ -158,11 +149,52 @@ ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModulePr
return result;
}
-QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &product)
+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.productItem->itemProperty(StringConstants::moduleProviders());
+ 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) {
@@ -177,6 +209,7 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &
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);
@@ -191,7 +224,7 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &
providerConfig.insert(name.toString(), m);
}
};
- configItemValue->item()->setScope(product.productItem);
+ configItemValue->item()->setScope(product.item);
collectMap(configItemValue->item(), QualifiedId());
}
for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) {
@@ -208,7 +241,7 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &
}
providerConfig.insert(provider, currentMapForProvider);
}
- return providerConfig;
+ product.providerConfig = providerConfig;
}
std::optional<std::vector<QualifiedId>> ModuleProviderLoader::getModuleProviders(Item *item)
@@ -245,9 +278,6 @@ QString ModuleProviderLoader::findModuleProviderFile(
fullPath = FileInfo::resolvePath(fullPath, component);
fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("provider.qbs"));
break;
- case ModuleProviderLookup::Fallback:
- fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback/provider.qbs"));
- break;
}
if (!FileInfo::exists(fullPath)) {
qCDebug(lcModuleLoader) << "No module provider found at" << fullPath;
@@ -258,14 +288,16 @@ QString ModuleProviderLoader::findModuleProviderFile(
return {};
}
-QVariantMap ModuleProviderLoader::evaluateQbsModule(const ProductContext &product) const
+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.productItem->property(StringConstants::qbsModule()));
+ product.item->property(StringConstants::qbsModule()));
QVariantMap result;
for (const auto &property : properties) {
const ScopedJsValue val(m_loaderState.evaluator().engine()->context(),
@@ -281,42 +313,38 @@ QVariantMap ModuleProviderLoader::evaluateQbsModule(const ProductContext &produc
result[property] = std::move(value);
}
- return result;
+ return *(product.providerQbsModule = result);
}
-Item *ModuleProviderLoader::createProviderScope(const ProductContext &product, const QVariantMap &qbsModule)
+Item *ModuleProviderLoader::createProviderScope(
+ const ProductContext &product, const QVariantMap &qbsModule)
{
const auto qbsItemValue = std::static_pointer_cast<ItemValue>(
- product.productItem->property(StringConstants::qbsModule()));
+ product.item->property(StringConstants::qbsModule()));
- Item *fakeQbsModule = Item::create(product.productItem->pool(), ItemType::Scope);
+ 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(product.productItem->pool(), ItemType::Scope);
+ Item *scope = Item::create(&m_loaderState.itemPool(), ItemType::Scope);
scope->setFile(qbsItemValue->item()->file());
scope->setProperty(StringConstants::qbsModule(), ItemValue::create(fakeQbsModule));
return scope;
}
-std::pair<QStringList, std::vector<ProbeConstPtr> > ModuleProviderLoader::evaluateModuleProvider(const ProductContext &product,
+ModuleProviderLoader::EvaluationResult ModuleProviderLoader::evaluateModuleProvider(
+ ProductContext &product,
const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
const QualifiedId &name,
const QString &providerFile,
const QVariantMap &moduleConfig,
const QVariantMap &qbsModule)
{
- 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.projectItem->variantProperty(
+ const QString projectBuildDir = product.project->item->variantProperty(
StringConstants::buildDirectoryProperty())->value().toString();
const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name);
@@ -324,25 +352,9 @@ std::pair<QStringList, std::vector<ProbeConstPtr> > ModuleProviderLoader::evalua
auto jsConfig = moduleConfig;
jsConfig[StringConstants::qbsModule()] = qbsModule;
- QTextStream stream(&dummyItemFile);
- using Qt::endl;
- setupDefaultCodec(stream);
- 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(jsConfig) << ')' << 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();
+ QString outputBaseDir = searchPathBaseDir + QLatin1Char('/') + getConfigHash(jsConfig);
Item * const providerItem = m_loaderState.itemReader().setupItemFromFile(
- dummyItemFile.fileName(), dependsItemLocation);
+ providerFile, dependsItemLocation);
if (providerItem->type() != ItemType::ModuleProvider) {
throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', "
"but '%3' was expected.")
@@ -350,17 +362,54 @@ std::pair<QStringList, std::vector<ProbeConstPtr> > ModuleProviderLoader::evalua
BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider)));
}
- providerItem->setScope(createProviderScope(product, qbsModule));
- providerItem->overrideProperties(moduleConfig, name, m_loaderState.parameters(),
- m_loaderState.logger());
- std::vector<ProbeConstPtr> probes = m_loaderState.probesResolver().resolveProbes(
- {product.name, product.uniqueName}, providerItem);
+ 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);
- return std::make_pair(m_loaderState.evaluator().stringListValue(
- providerItem, QStringLiteral("searchPaths")),
- std::move(probes));
+
+ 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
diff --git a/src/lib/corelib/loader/moduleproviderloader.h b/src/lib/corelib/loader/moduleproviderloader.h
index 221830f22..471cfbb02 100644
--- a/src/lib/corelib/loader/moduleproviderloader.h
+++ b/src/lib/corelib/loader/moduleproviderloader.h
@@ -46,87 +46,65 @@
#include <language/forward_decls.h>
#include <language/moduleproviderinfo.h>
-#include <QtCore/qmap.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);
- enum class ModuleProviderLookup { Scoped, Named, Fallback };
-
- struct Provider
- {
- QualifiedId name;
- ModuleProviderLookup lookup;
- };
-
- struct ModuleProviderResult
- {
- std::vector<ProbeConstPtr> probes;
- QVariantMap providerConfig;
+ struct ModuleProviderResult {
bool providerFound = false;
std::optional<QStringList> searchPaths;
};
-
- const StoredModuleProviderInfo &storedModuleProviderInfo() const
- {
- return m_storedModuleProviderInfo;
- }
-
- void setStoredModuleProviderInfo(StoredModuleProviderInfo moduleProviderInfo)
- {
- m_storedModuleProviderInfo = std::move(moduleProviderInfo);
- }
-
- const Set<QString> &tempQbsFiles() const { return m_tempQbsFiles; }
-
- struct ProductContext {
- Item * const productItem;
- const Item * const projectItem;
- const QString &name;
- const QString &uniqueName;
- const QVariantMap &moduleProperties;
- const std::optional<QVariantMap> providerConfig;
- };
ModuleProviderResult executeModuleProviders(
- const ProductContext &productContext,
- const CodeLocation &dependsItemLocation,
- const QualifiedId &moduleName,
- FallbackMode fallbackMode);
+ ProductContext &productContext,
+ const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName);
private:
+ enum class ModuleProviderLookup { Scoped, Named };
+ struct Provider {
+ QualifiedId name;
+ ModuleProviderLookup lookup;
+ };
ModuleProviderResult executeModuleProvidersHelper(
- const ProductContext &product,
+ ProductContext &product,
const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName,
const std::vector<Provider> &providers);
- QVariantMap getModuleProviderConfig(const ProductContext &product);
+ 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(const ProductContext &product) const;
+ QVariantMap evaluateQbsModule(ProductContext &product) const;
Item *createProviderScope(const ProductContext &product, const QVariantMap &qbsModule);
- std::pair<QStringList, std::vector<ProbeConstPtr>> evaluateModuleProvider(
- const ProductContext &product,
- const CodeLocation &location,
+ 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);
-private:
LoaderState &m_loaderState;
- StoredModuleProviderInfo m_storedModuleProviderInfo;
- Set<QString> m_tempQbsFiles;
};
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/probesresolver.cpp b/src/lib/corelib/loader/probesresolver.cpp
index 763f3ed29..269e9c4b6 100644
--- a/src/lib/corelib/loader/probesresolver.cpp
+++ b/src/lib/corelib/loader/probesresolver.cpp
@@ -83,40 +83,29 @@ static QString probeGlobalId(Item *probe)
ProbesResolver::ProbesResolver(LoaderState &loaderState) : m_loaderState(loaderState) {}
-void ProbesResolver::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
-{
- m_oldProjectProbes.clear();
- for (const ProbeConstPtr& probe : oldProbes)
- m_oldProjectProbes[probe->globalId()] << probe;
-}
-
-void ProbesResolver::setOldProductProbes(
- const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
-{
- m_oldProductProbes = oldProbes;
-}
-
-std::vector<ProbeConstPtr> ProbesResolver::resolveProbes(const ProductContext &productContext, Item *item)
+void ProbesResolver::resolveProbes(ProductContext &productContext, Item *item)
{
AccumulatingTimer probesTimer(m_loaderState.parameters().logElapsedTime()
- ? &m_elapsedTimeProbes : nullptr);
+ ? &productContext.timingData.probes : nullptr);
+
EvalContextSwitcher evalContextSwitcher(m_loaderState.evaluator().engine(),
EvalContext::ProbeExecution);
- std::vector<ProbeConstPtr> probes;
- for (Item * const child : item->children())
+ for (Item * const child : item->children()) {
if (child->type() == ItemType::Probe)
- probes.push_back(resolveProbe(productContext, item, child));
- return probes;
+ resolveProbe(productContext, item, child);
+ }
}
-ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext, Item *parent,
+void ProbesResolver::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 bool isProjectLevelProbe
+ = parent->type() == ItemType::Project
+ || productContext.name.startsWith(StringConstants::shadowProductPrefix());
const JSSourceValueConstPtr configureScript
= probe->sourceProperty(StringConstants::configureProperty());
QBS_CHECK(configureScript);
@@ -143,29 +132,30 @@ ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext,
const bool condition = evaluator.boolValue(probe, StringConstants::conditionProperty());
const QString &sourceCode = configureScript->sourceCode().toString();
ProbeConstPtr resolvedProbe;
- if (parent->type() == ItemType::Project
- || productContext.name.startsWith(StringConstants::shadowProductPrefix())) {
+ 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,
+ 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_probesCachedCurrent;
+ m_loaderState.topLevelProject().incrementReusedCurrentProbesCount();
}
} else {
qCDebug(lcModuleLoader) << "probe results cached from earlier run";
- ++m_probesCachedOld;
+ m_loaderState.topLevelProject().incrementReusedOldProbesCount();
}
ScopedJsValue configureScope(ctx, JS_UNDEFINED);
std::vector<QString> importedFilesUsedInConfigure;
if (!condition) {
qCDebug(lcModuleLoader) << "Probe disabled; skipping";
} else if (!resolvedProbe) {
- ++m_probesRun;
+ m_loaderState.topLevelProject().incrementRunProbesCount();
qCDebug(lcModuleLoader) << "configure script needs to run";
const Evaluator::FileContextScopes fileCtxScopes
= evaluator.fileContextScopes(configureScript->file());
@@ -183,6 +173,8 @@ ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext,
importedFilesUsedInConfigure = resolvedProbe->importedFilesUsed();
}
QVariantMap properties;
+ VariantValuePtr storedValue;
+ QMap<QString, VariantValuePtr> storedValues;
for (const ProbeProperty &b : probeBindings) {
QVariant newValue;
if (resolvedProbe) {
@@ -206,22 +198,45 @@ ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext,
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 (newValue != getJsVariant(ctx, b.second))
- probe->setProperty(b.first, VariantValue::create(newValue));
- if (!resolvedProbe)
+ 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,
+ sourceCode, properties, initialProperties, storedValues,
importedFilesUsedInConfigure);
- m_currentProbes[probe->location()] << resolvedProbe;
+ m_loaderState.topLevelProject().addNewlyResolvedProbe(resolvedProbe);
}
- return resolvedProbe;
+ if (isProjectLevelProbe)
+ m_loaderState.topLevelProject().addProjectLevelProbe(resolvedProbe);
+ else
+ productContext.probes << resolvedProbe;
}
ProbeConstPtr ProbesResolver::findOldProjectProbe(
@@ -232,13 +247,10 @@ ProbeConstPtr ProbesResolver::findOldProjectProbe(
{
if (m_loaderState.parameters().forceProbeExecution())
return {};
-
- for (const ProbeConstPtr &oldProbe : m_oldProjectProbes.value(globalId)) {
- if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes))
- return oldProbe;
- }
-
- return {};
+ return m_loaderState.topLevelProject().findOldProjectProbe(globalId,
+ [&](const ProbeConstPtr &oldProbe) {
+ return probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes);
+ });
}
ProbeConstPtr ProbesResolver::findOldProductProbe(
@@ -249,13 +261,10 @@ ProbeConstPtr ProbesResolver::findOldProductProbe(
{
if (m_loaderState.parameters().forceProbeExecution())
return {};
-
- for (const ProbeConstPtr &oldProbe : m_oldProductProbes.value(productName)) {
- if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes))
- return oldProbe;
- }
-
- return {};
+ return m_loaderState.topLevelProject().findOldProductProbe(productName,
+ [&](const ProbeConstPtr &oldProbe) {
+ return probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes);
+ });
}
ProbeConstPtr ProbesResolver::findCurrentProbe(
@@ -263,12 +272,10 @@ ProbeConstPtr ProbesResolver::findCurrentProbe(
bool condition,
const QVariantMap &initialProperties) const
{
- const std::vector<ProbeConstPtr> &cachedProbes = m_currentProbes.value(location);
- for (const ProbeConstPtr &probe : cachedProbes) {
- if (probeMatches(probe, condition, initialProperties, QString(), CompareScript::No))
- return probe;
- }
- return {};
+ 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,
@@ -276,26 +283,10 @@ bool ProbesResolver::probeMatches(const ProbeConstPtr &probe, bool condition,
CompareScript compareScript) const
{
return probe->condition() == condition
- && probe->initialProperties() == initialProperties
- && (compareScript == CompareScript::No
- || (probe->configureScript() == configureScript
- && !probe->needsReconfigure(m_lastResolveTime)));
-}
-
-void ProbesResolver::printProfilingInfo(int indent)
-{
- if (!m_loaderState.parameters().logElapsedTime())
- return;
- const QByteArray prefix(indent, ' ');
- m_loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("Running Probes took %1.").arg(elapsedTimeString(m_elapsedTimeProbes));
- m_loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << 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);
+ && qVariantMapsEqual(probe->initialProperties(), initialProperties)
+ && (compareScript == CompareScript::No
+ || (probe->configureScript() == configureScript
+ && !probe->needsReconfigure(m_loaderState.topLevelProject().lastResolveTime())));
}
} // namespace Internal
diff --git a/src/lib/corelib/loader/probesresolver.h b/src/lib/corelib/loader/probesresolver.h
index 3e304eec6..4c49861d3 100644
--- a/src/lib/corelib/loader/probesresolver.h
+++ b/src/lib/corelib/loader/probesresolver.h
@@ -42,30 +42,20 @@
#define PROBESRESOLVER_H
#include <language/forward_decls.h>
-
-#include <tools/filetime.h>
+#include <tools/codelocation.h>
#include <QString>
-#include <vector>
-
namespace qbs::Internal {
class Item;
class LoaderState;
+class ProductContext;
class ProbesResolver
{
public:
explicit ProbesResolver(LoaderState &loaderState);
- void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
- void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void printProfilingInfo(int indent);
-
- struct ProductContext {
- const QString &name;
- const QString &uniqueName;
- };
- std::vector<ProbeConstPtr> resolveProbes(const ProductContext &productContext, Item *item);
+ void resolveProbes(ProductContext &productContext, Item *item);
private:
ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition,
@@ -80,19 +70,9 @@ private:
bool probeMatches(const ProbeConstPtr &probe, bool condition,
const QVariantMap &initialProperties, const QString &configureScript,
CompareScript compareScript) const;
- ProbeConstPtr resolveProbe(const ProductContext &productContext, Item *parent, Item *probe);
-
- qint64 m_elapsedTimeProbes = 0;
- quint64 m_probesEncountered = 0;
- quint64 m_probesRun = 0;
- quint64 m_probesCachedCurrent = 0;
- quint64 m_probesCachedOld = 0;
+ void resolveProbe(ProductContext &productContext, Item *parent, Item *probe);
LoaderState &m_loaderState;
- QHash<QString, std::vector<ProbeConstPtr>> m_oldProjectProbes;
- QHash<QString, std::vector<ProbeConstPtr>> m_oldProductProbes;
- FileTime m_lastResolveTime;
- QHash<CodeLocation, std::vector<ProbeConstPtr>> m_currentProbes;
};
} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productitemmultiplexer.cpp b/src/lib/corelib/loader/productitemmultiplexer.cpp
index f18c37caf..0adb3d7fd 100644
--- a/src/lib/corelib/loader/productitemmultiplexer.cpp
+++ b/src/lib/corelib/loader/productitemmultiplexer.cpp
@@ -40,6 +40,7 @@
#include "productitemmultiplexer.h"
#include "loaderutils.h"
+#include "moduleinstantiator.h"
#include <language/evaluator.h>
#include <language/item.h>
@@ -51,14 +52,14 @@
#include <tools/stringconstants.h>
#include <QJsonDocument>
-#include <QThreadStorage>
#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 MultiplexConfigurationByIdTable = QThreadStorage<QHash<QString, QVariantMap>>;
using MultiplexRow = std::vector<VariantValuePtr>;
using MultiplexTable = std::vector<MultiplexRow>;
class MultiplexInfo
@@ -69,54 +70,50 @@ public:
bool aggregate = false;
VariantValuePtr multiplexedType;
- QString toIdString(size_t row) const;
+ QString toIdString(size_t row, LoaderState &loaderState) const;
};
} // namespace
-Q_GLOBAL_STATIC(MultiplexConfigurationByIdTable, multiplexConfigurationsById);
-
-class ProductItemMultiplexer::Private
+class ProductItemMultiplexer
{
public:
- Private(LoaderState &loaderState, QbsItemRetriever qbsItemRetriever)
- : loaderState(loaderState), qbsItemRetriever(std::move(qbsItemRetriever)) {}
-
- MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem);
+ 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 &loaderState;
- const QbsItemRetriever qbsItemRetriever;
+ LoaderState &m_loaderState;
+ const QString &m_productName;
+ Item * const m_productItem;
+ Item * const m_tempQbsModuleItem;
+ const std::function<void()> &m_dropTempQbsModule;
};
-ProductItemMultiplexer::ProductItemMultiplexer(LoaderState &loaderState,
- const QbsItemRetriever &qbsItemRetriever)
- : d(makePimpl<Private>(loaderState, qbsItemRetriever)) {}
-
-ProductItemMultiplexer::~ProductItemMultiplexer() = default;
-
-QList<Item *> ProductItemMultiplexer::multiplex(
- const QString &productName,
- Item *productItem,
- Item *tempQbsModuleItem,
- const std::function<void ()> &dropTempQbsModule)
+QList<Item *> ProductItemMultiplexer::multiplex()
{
- const auto multiplexInfo = d->extractMultiplexInfo(productItem, tempQbsModuleItem);
- dropTempQbsModule();
+ const auto multiplexInfo = extractMultiplexInfo();
+ m_dropTempQbsModule();
if (multiplexInfo.table.size() > 1)
- productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue());
- VariantValuePtr productNameValue = VariantValue::create(productName);
- Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr;
+ 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 = productItem;
+ Item *item = m_productItem;
const auto &mprow = multiplexInfo.table.at(row);
QBS_CHECK(mprow.size() == multiplexInfo.properties.size());
if (row > 0) {
- item = productItem->clone();
+ item = m_productItem->clone(m_loaderState.itemPool());
additionalProductItems.push_back(item);
}
- const QString multiplexConfigurationId = multiplexInfo.toIdString(row);
+ const QString multiplexConfigurationId = multiplexInfo.toIdString(row, m_loaderState);
const VariantValuePtr multiplexConfigurationIdValue
= VariantValue::create(multiplexConfigurationId);
if (multiplexInfo.table.size() > 1 || aggregator) {
@@ -127,7 +124,7 @@ QList<Item *> ProductItemMultiplexer::multiplex(
if (multiplexInfo.multiplexedType)
item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType);
for (size_t column = 0; column < mprow.size(); ++column) {
- Item * const qbsItem = d->qbsItemRetriever(item);
+ Item * const qbsItem = retrieveQbsItem(item, m_loaderState);
const QString &propertyName = multiplexInfo.properties.at(column);
const VariantValuePtr &mpvalue = mprow.at(column);
qbsItem->setProperty(propertyName, mpvalue);
@@ -139,14 +136,14 @@ QList<Item *> ProductItemMultiplexer::multiplex(
// Add dependencies to all multiplexed instances.
for (const auto &v : multiplexConfigurationIdValues) {
- Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends);
+ 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(d->loaderState.parameters().deprecationWarningMode(),
- d->loaderState.logger());
+ dependsItem->setupForBuiltinType(m_loaderState.parameters().deprecationWarningMode(),
+ m_loaderState.logger());
Item::addChild(aggregator, dependsItem);
}
}
@@ -154,23 +151,22 @@ QList<Item *> ProductItemMultiplexer::multiplex(
return additionalProductItems;
}
-MultiplexInfo ProductItemMultiplexer::Private::extractMultiplexInfo(Item *productItem,
- Item *qbsModuleItem)
+MultiplexInfo ProductItemMultiplexer::extractMultiplexInfo()
{
static const QString mpmKey = QStringLiteral("multiplexMap");
- Evaluator &evaluator = loaderState.evaluator();
+ Evaluator &evaluator = m_loaderState.evaluator();
JSContext * const ctx = evaluator.engine()->context();
- const ScopedJsValue multiplexMap(ctx, evaluator.value(qbsModuleItem, mpmKey));
+ const ScopedJsValue multiplexMap(ctx, evaluator.value(m_tempQbsModuleItem, mpmKey));
const QStringList multiplexByQbsProperties = evaluator.stringListValue(
- productItem, StringConstants::multiplexByQbsPropertiesProperty());
+ m_productItem, StringConstants::multiplexByQbsPropertiesProperty());
MultiplexInfo multiplexInfo;
multiplexInfo.aggregate = evaluator.boolValue(
- productItem, StringConstants::aggregateProperty());
+ m_productItem, StringConstants::aggregateProperty());
const QString multiplexedType = evaluator.stringValue(
- productItem, StringConstants::multiplexedTypeProperty());
+ m_productItem, StringConstants::multiplexedTypeProperty());
if (!multiplexedType.isEmpty())
multiplexInfo.multiplexedType = VariantValue::create(multiplexedType);
@@ -183,7 +179,7 @@ MultiplexInfo ProductItemMultiplexer::Private::extractMultiplexInfo(Item *produc
if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second)
continue;
- const ScopedJsValue arr(ctx, evaluator.value(qbsModuleItem, key));
+ const ScopedJsValue arr(ctx, evaluator.value(m_tempQbsModuleItem, key));
if (JS_IsUndefined(arr))
continue;
if (!JS_IsArray(ctx, arr))
@@ -210,8 +206,8 @@ MultiplexInfo ProductItemMultiplexer::Private::extractMultiplexInfo(Item *produc
return multiplexInfo;
}
-MultiplexTable ProductItemMultiplexer::Private::combine(const MultiplexTable &table,
- const MultiplexRow &values)
+MultiplexTable ProductItemMultiplexer::combine(const MultiplexTable &table,
+ const MultiplexRow &values)
{
MultiplexTable result;
if (table.empty()) {
@@ -234,31 +230,7 @@ MultiplexTable ProductItemMultiplexer::Private::combine(const MultiplexTable &ta
return result;
}
-QVariantMap ProductItemMultiplexer::multiplexIdToVariantMap(const QString &multiplexId)
-{
- if (multiplexId.isEmpty())
- return QVariantMap();
-
- // We assume that MultiplexInfo::toIdString() has been called for this
- // particular multiplex configuration.
- QVariantMap result = multiplexConfigurationsById->localData().value(multiplexId);
- QBS_CHECK(!result.isEmpty());
- return result;
-}
-
-QString ProductItemMultiplexer::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;
-}
-
-QString MultiplexInfo::toIdString(size_t row) const
+QString MultiplexInfo::toIdString(size_t row, LoaderState &loaderState) const
{
const auto &mprow = table.at(row);
QVariantMap multiplexConfiguration;
@@ -272,9 +244,16 @@ QString MultiplexInfo::toIdString(size_t row) const
.toBase64());
// Cache for later use in multiplexIdToVariantMap()
- multiplexConfigurationsById->localData().insert(id, multiplexConfiguration);
+ 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
index 53eb1a722..e02e21793 100644
--- a/src/lib/corelib/loader/productitemmultiplexer.h
+++ b/src/lib/corelib/loader/productitemmultiplexer.h
@@ -39,42 +39,26 @@
#pragma once
-#include <tools/pimpl.h>
-
#include <QList>
-#include <QVariantMap>
#include <functional>
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
namespace qbs::Internal {
class Item;
class LoaderState;
-// This class 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.
-class ProductItemMultiplexer
-{
-public:
- using QbsItemRetriever = std::function<Item *(Item *)>;
- ProductItemMultiplexer(LoaderState &loaderState, const QbsItemRetriever &qbsItemRetriever);
- ~ProductItemMultiplexer();
-
- // 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
- );
-
- static QVariantMap multiplexIdToVariantMap(const QString &multiplexId);
- static QString fullProductDisplayName(const QString &name, const QString &multiplexId);
-
-private:
- class Private;
- Pimpl<Private> d;
-};
+// 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 &currentValues);
+ 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 &parameters, 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 &parameters = 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 &parameters) const { check(parameters, QualifiedId()); }
+
+private:
+ void check(const QVariantMap &parameters, 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 &currentValues)
+{
+ // 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 &regex)
+{
+ return prop.sourceCode.indexOf(regex) != -1;
+}
+
+static bool usesImport(const ExportedItem &item, const QRegularExpression &regex)
+{
+ 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 &parameters, 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/productshandler.h b/src/lib/corelib/loader/productresolver.h
index f1fc74a20..34c49b2bb 100644
--- a/src/lib/corelib/loader/productshandler.h
+++ b/src/lib/corelib/loader/productresolver.h
@@ -39,28 +39,19 @@
#pragma once
-#include <tools/pimpl.h>
-
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.
-// - Preparing Group items for property evaluation.
-class ProductsHandler
-{
-public:
- ProductsHandler(LoaderState &loaderState);
- ~ProductsHandler();
-
- void run();
- void printProfilingInfo(int indent);
+// - 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);
-private:
- class Private;
- Pimpl<Private> d;
-};
+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
index 95b248c73..042dbd160 100644
--- a/src/lib/corelib/loader/productscollector.cpp
+++ b/src/lib/corelib/loader/productscollector.cpp
@@ -73,9 +73,11 @@ class ProductsCollector::Private
public:
Private(LoaderState &loaderState) : loaderState(loaderState) {}
- void handleProject(Item *projectItem, const Set<QString> &referencedFilePaths);
+ void handleProject(Item *projectItem, ProjectContext *parentProject,
+ const Set<QString> &referencedFilePaths);
QList<Item *> multiplexProductItem(ProductContext &dummyContext, Item *productItem);
- void prepareProduct(ProjectContext &projectContext, 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);
@@ -86,17 +88,18 @@ public:
bool checkExportItemCondition(Item *exportItem, const ProductContext &product);
void initProductProperties(const ProductContext &product);
void checkProjectNamesInOverrides();
- void collectProductsByName();
+ 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;
- qint64 elapsedTimePrepareProducts = 0;
private:
+ // TODO: Put this in loaderutils
class TempBaseModuleAttacher {
public:
TempBaseModuleAttacher(Private *d, ProductContext &product);
@@ -118,24 +121,15 @@ ProductsCollector::~ProductsCollector() = default;
void ProductsCollector::run(Item *rootProject)
{
- d->handleProject(rootProject, {QDir::cleanPath(d->loaderState.parameters().projectFilePath())});
+ d->handleProject(rootProject, nullptr, {rootProject->file()->filePath()});
d->checkProjectNamesInOverrides();
- d->collectProductsByName();
+ d->collectProductsByNameAndItem();
d->checkProductNamesInOverrides();
+ d->loaderState.topLevelProject().checkForLocalProfileAsTopLevelProfile(
+ d->loaderState.parameters().topLevelProfile());
}
-void ProductsCollector::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- const QByteArray prefix(indent, ' ');
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("Preparing products took %1.")
- .arg(elapsedTimeString(d->elapsedTimePrepareProducts));
-}
-
-void ProductsCollector::Private::handleProject(Item *projectItem,
+void ProductsCollector::Private::handleProject(Item *projectItem, ProjectContext *parentProject,
const Set<QString> &referencedFilePaths)
{
const SetupProjectParameters &parameters = loaderState.parameters();
@@ -146,9 +140,11 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
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(projectItem->pool(), ItemType::Scope);
+ projectContext.scope = Item::create(&loaderState.itemPool(), ItemType::Scope);
projectContext.scope->setFile(projectItem->file());
projectContext.scope->setProperty(StringConstants::projectVar(), itemValue);
ProductContext dummyProduct;
@@ -156,7 +152,7 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
dummyProduct.moduleProperties = parameters.finalBuildConfigurationTree();
dummyProduct.profileModuleProperties = dummyProduct.moduleProperties;
dummyProduct.profileName = parameters.topLevelProfile();
- loaderState.dependenciesResolver().loadBaseModule(dummyProduct, projectItem);
+ loadBaseModule(dummyProduct, projectItem, loaderState);
projectItem->overrideProperties(parameters.overriddenValuesTree(),
StringConstants::projectPrefix(), parameters, logger);
@@ -166,6 +162,9 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
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);
@@ -173,11 +172,9 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
disabledProjects.insert(projectContext.name);
return;
}
- topLevelProject.projects.push_back(p.release());
SearchPathsManager searchPathsManager(itemReader, itemReader.readExtraSearchPaths(projectItem)
<< projectItem->file()->dirPath());
projectContext.searchPathsStack = itemReader.extraSearchPathsStack();
- projectContext.item = projectItem;
const QString minVersionStr
= evaluator.stringValue(projectItem, StringConstants::minimumQbsVersionProperty(),
@@ -199,10 +196,9 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
for (Item * const child : projectItem->children())
child->setScope(projectContext.scope);
- projectContext.topLevelProject->probes << loaderState.probesResolver().resolveProbes(
- {dummyProduct.name, dummyProduct.uniqueName()}, projectItem);
+ ProbesResolver(loaderState).resolveProbes(dummyProduct, projectItem);
- loaderState.localProfiles().collectProfilesFromItems(projectItem, projectContext.scope);
+ collectProfilesFromItems(projectItem, projectContext.scope, loaderState);
QList<Item *> multiplexedProducts;
for (Item * const child : projectItem->children()) {
@@ -223,7 +219,7 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
break;
case ItemType::Project:
copyProperties(projectItem, child);
- handleProject(child, referencedFilePaths);
+ handleProject(child, &projectContext, referencedFilePaths);
break;
default:
break;
@@ -253,7 +249,7 @@ void ProductsCollector::Private::handleProject(Item *projectItem,
break;
case ItemType::Project:
copyProperties(projectItem, subItem);
- handleProject(subItem,
+ handleProject(subItem, &projectContext,
Set<QString>(referencedFilePaths) << subItem->file()->filePath());
break;
default:
@@ -278,28 +274,36 @@ QList<Item *> ProductsCollector::Private::multiplexProductItem(ProductContext &d
loaderState.parameters(), loaderState.logger());
dummyContext.item = productItem;
TempBaseModuleAttacher tbma(this, dummyContext);
- return loaderState.multiplexer().multiplex(productName, productItem, tbma.tempBaseModuleItem(),
- [&] { tbma.drop(); });
+ return multiplex(productName, productItem, tbma.tempBaseModuleItem(),
+ [&] { tbma.drop(); }, loaderState);
}
-void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext, Item *productItem)
+void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext, Item *productItem,
+ ProductContext *mainProduct)
{
const SetupProjectParameters &parameters = loaderState.parameters();
Evaluator &evaluator = loaderState.evaluator();
TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
- AccumulatingTimer timer(parameters.logElapsedTime() ? &elapsedTimePrepareProducts : nullptr);
- topLevelProject.checkCancelation(parameters);
+ AccumulatingTimer timer(parameters.logElapsedTime()
+ ? &topLevelProject.timingData().preparingProducts : nullptr);
+ topLevelProject.checkCancelation();
qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath();
- ProductContext productContext;
+ 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());
+ const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule(),
+ loaderState.itemPool());
if (qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) {
TempBaseModuleAttacher tbma(this, productContext);
productContext.profileName = evaluator.stringValue(
@@ -312,11 +316,11 @@ void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext,
QBS_CHECK(!productContext.profileName.isEmpty());
// Set up full module property map based on the profile.
- const auto it = topLevelProject.profileConfigs.constFind(productContext.profileName);
- QVariantMap flatConfig;
- if (it == topLevelProject.profileConfigs.constEnd()) {
+ std::optional<QVariantMap> flatConfig
+ = topLevelProject.profileConfig(productContext.profileName);
+ if (!flatConfig) {
const Profile profile(productContext.profileName, &settings,
- loaderState.localProfiles().profiles());
+ loaderState.topLevelProject().localProfiles());
if (!profile.exists()) {
ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()),
productItem->location());
@@ -325,37 +329,50 @@ void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext,
}
flatConfig = SetupProjectParameters::expandedBuildConfiguration(
profile, parameters.configurationName());
- topLevelProject.profileConfigs.insert(productContext.profileName, flatConfig);
- } else {
- flatConfig = it.value().toMap();
+ topLevelProject.addProfileConfig(productContext.profileName, *flatConfig);
}
productContext.profileModuleProperties = SetupProjectParameters::finalBuildConfigurationTree(
- flatConfig, {});
+ *flatConfig, {});
productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree(
- flatConfig, parameters.overriddenValues());
+ *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(productItem->pool(), ItemType::Scope);
+ 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);
- projectContext.products.push_back(productContext);
-
- if (!hasExportItems || getShadowProductInfo(productContext).first)
+ 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(productItem->pool(), ItemType::Product);
+ Item * const importer = Item::create(&loaderState.itemPool(), ItemType::Product);
importer->setProperty(QStringLiteral("name"),
VariantValue::create(StringConstants::shadowProductPrefix()
+ productContext.name));
@@ -363,15 +380,16 @@ void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext,
importer->setLocation(productItem->location());
importer->setScope(projectContext.scope);
importer->setupForBuiltinType(parameters.deprecationWarningMode(), loaderState.logger());
- Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends);
+ 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);
- Item::addChild(productItem, importer);
- prepareProduct(projectContext, importer);
+ prepareProduct(projectContext, importer, &productContext);
}
void ProductsCollector::Private::handleSubProject(
@@ -426,7 +444,7 @@ void ProductsCollector::Private::handleSubProject(
Item::addChild(projectItem, loadedItem);
projectItem->setScope(projectContext.scope);
- handleProject(loadedItem, Set<QString>(referencedFilePaths) << subProjectFilePath);
+ handleProject(loadedItem, &projectContext, Set<QString>(referencedFilePaths) << subProjectFilePath);
}
void ProductsCollector::Private::copyProperties(const Item *sourceProject, Item *targetProject)
@@ -507,18 +525,19 @@ QList<Item *> ProductsCollector::Private::loadReferencedFile(
QList<Item *> loadedItems;
loadedItems << subItem;
if (subItem->type() == ItemType::Product) {
- loaderState.localProfiles().collectProfilesFromItems(subItem, dummyContext.project->scope);
+ collectProfilesFromItems(subItem, dummyContext.project->scope, loaderState);
loadedItems << multiplexProductItem(dummyContext, subItem);
}
return loadedItems;
}
-static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
+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)->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;
@@ -533,7 +552,7 @@ static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
if (baseValue) {
QBS_CHECK(baseValue->type() == Value::JSSourceValueType);
const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>(
- baseValue->clone());
+ baseValue->clone(loaderState.itemPool()));
jsValue->setBaseValue(jsBaseValue);
std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives();
jsValue->clearAlternatives();
@@ -566,14 +585,14 @@ bool ProductsCollector::Private::mergeExportItems(ProductContext &productContext
if (!exportItems.empty())
productContext.item->setChildren(children);
- Item *merged = Item::create(productContext.item->pool(), ItemType::Export);
+ 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(parameters);
+ topLevelProject.checkCancelation();
if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file())))
throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."),
exportItem->location());
@@ -584,9 +603,9 @@ bool ProductsCollector::Private::mergeExportItems(ProductContext &productContext
for (Item * const child : exportItem->children()) {
if (child->type() == ItemType::Parameters) {
adjustParametersScopes(child, child);
- mergeParameters(defaultParameters,
- getJsVariant(evaluator.engine()->context(),
- evaluator.scriptValue(child)).toMap());
+ defaultParameters = mergeDependencyParameters(defaultParameters,
+ getJsVariant(evaluator.engine()->context(),
+ evaluator.scriptValue(child)).toMap());
} else {
Item::addChild(merged, child);
}
@@ -626,11 +645,11 @@ bool ProductsCollector::Private::checkExportItemCondition(Item *exportItem,
{
class ScopeHandler {
public:
- ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem)
- : m_exportItem(exportItem)
+ ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem,
+ ItemPool &itemPool) : m_exportItem(exportItem), m_itemPool(itemPool)
{
if (!*cachedScopeItem)
- *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope);
+ *cachedScopeItem = Item::create(&m_itemPool, ItemType::Scope);
Item * const scope = *cachedScopeItem;
QBS_CHECK(productContext.item->file());
scope->setFile(productContext.item->file());
@@ -644,7 +663,8 @@ bool ProductsCollector::Private::checkExportItemCondition(Item *exportItem,
private:
Item * const m_exportItem;
- } scopeHandler(exportItem, product, &tempScopeItem);
+ ItemPool &m_itemPool;
+ } scopeHandler(exportItem, product, &tempScopeItem, loaderState.itemPool());
return loaderState.topLevelProject().checkItemCondition(exportItem, loaderState.evaluator());
}
@@ -652,7 +672,7 @@ void ProductsCollector::Private::initProductProperties(const ProductContext &pro
{
QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name,
product.multiplexConfigurationId);
- buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir);
+ 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();
@@ -663,10 +683,10 @@ void ProductsCollector::Private::initProductProperties(const ProductContext &pro
void ProductsCollector::Private::checkProjectNamesInOverrides()
{
for (const QString &projectNameInOverride
- : loaderState.topLevelProject().projectNamesUsedInOverrides) {
+ : loaderState.topLevelProject().projectNamesUsedInOverrides()) {
if (disabledProjects.contains(projectNameInOverride))
continue;
- if (!any_of(loaderState.topLevelProject().projects,
+ if (!any_of(loaderState.topLevelProject().projects(),
[&projectNameInOverride](const ProjectContext *p) {
return p->name == projectNameInOverride; })) {
handlePropertyError(Tr::tr("Unknown project '%1' in property override.")
@@ -676,28 +696,26 @@ void ProductsCollector::Private::checkProjectNamesInOverrides()
}
}
-void ProductsCollector::Private::collectProductsByName()
+void ProductsCollector::Private::collectProductsByNameAndItem()
{
TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
- for (ProjectContext * const project : topLevelProject.projects) {
+ for (ProjectContext * const project : topLevelProject.projects()) {
for (ProductContext &product : project->products)
- topLevelProject.productsByName.insert({product.name, &product});
+ topLevelProject.addProduct(product);
}
}
void ProductsCollector::Private::checkProductNamesInOverrides()
{
TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
- for (const QString &productNameInOverride : topLevelProject.productNamesUsedInOverrides) {
- if (topLevelProject.erroneousProducts.contains(productNameInOverride))
- continue;
- if (!any_of(topLevelProject.productsByName, [&productNameInOverride](
- const std::pair<QString, ProductContext *> &elem) {
+ 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 elem.first == productNameInOverride
- || elem.first.startsWith(productNameInOverride + StringConstants::dot());
+ return product.name == productNameInOverride
+ || product.name.startsWith(productNameInOverride + StringConstants::dot());
})) {
handlePropertyError(Tr::tr("Unknown product '%1' in property override.")
.arg(productNameInOverride),
@@ -714,9 +732,9 @@ ProductsCollector::Private::TempBaseModuleAttacher::TempBaseModuleAttacher(
// Cloning is necessary because the original value will get "instantiated" now.
if (qbsValue)
- m_origQbsValue = qbsValue->clone();
+ m_origQbsValue = qbsValue->clone(d->loaderState.itemPool());
- m_tempBaseModule = d->loaderState.dependenciesResolver().loadBaseModule(product, m_productItem);
+ m_tempBaseModule = loadBaseModule(product, m_productItem, d->loaderState);
}
void ProductsCollector::Private::TempBaseModuleAttacher::drop()
diff --git a/src/lib/corelib/loader/productscollector.h b/src/lib/corelib/loader/productscollector.h
index fa2059872..246361b37 100644
--- a/src/lib/corelib/loader/productscollector.h
+++ b/src/lib/corelib/loader/productscollector.h
@@ -54,7 +54,6 @@ public:
~ProductsCollector();
void run(Item *rootProject);
- void printProfilingInfo(int indent);
private:
class Private;
diff --git a/src/lib/corelib/loader/productshandler.cpp b/src/lib/corelib/loader/productshandler.cpp
deleted file mode 100644
index e0ca79acc..000000000
--- a/src/lib/corelib/loader/productshandler.cpp
+++ /dev/null
@@ -1,336 +0,0 @@
-/****************************************************************************
-**
-** 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 "productshandler.h"
-
-#include "dependenciesresolver.h"
-#include "groupshandler.h"
-#include "itemreader.h"
-#include "loaderutils.h"
-#include "modulepropertymerger.h"
-#include "probesresolver.h"
-
-#include <language/evaluator.h>
-#include <language/item.h>
-#include <language/value.h>
-#include <logging/categories.h>
-#include <logging/translator.h>
-#include <tools/profiling.h>
-#include <tools/setupprojectparameters.h>
-#include <tools/stringconstants.h>
-
-namespace qbs::Internal {
-
-class ProductsHandler::Private
-{
-public:
- Private(LoaderState &loaderState) : loaderState(loaderState) {}
-
- void handleNextProduct();
- void handleProduct(ProductContext &product, Deferral deferral);
- void resolveProbes(ProductContext &product);
- void resolveProbes(ProductContext &product, Item *item);
- void handleModuleSetupError(ProductContext &product, const Item::Module &module,
- const ErrorInfo &error);
- void runModuleProbes(ProductContext &product, const Item::Module &module);
- bool validateModule(ProductContext &product, const Item::Module &module);
- void updateModulePresentState(ProductContext &product, const Item::Module &module);
- void handleGroups(ProductContext &product);
-
- LoaderState &loaderState;
- GroupsHandler groupsHandler{loaderState};
- qint64 elapsedTime = 0;
-};
-
-ProductsHandler::ProductsHandler(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) {}
-
-void ProductsHandler::run()
-{
- AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
- ? &d->elapsedTime : nullptr);
-
- TopLevelProjectContext &topLevelProject = d->loaderState.topLevelProject();
- for (ProjectContext * const projectContext : topLevelProject.projects) {
- for (ProductContext &productContext : projectContext->products)
- topLevelProject.productsToHandle.emplace_back(&productContext, -1);
- }
- while (!topLevelProject.productsToHandle.empty())
- d->handleNextProduct();
-}
-
-void ProductsHandler::printProfilingInfo(int indent)
-{
- if (!d->loaderState.parameters().logElapsedTime())
- return;
- const QByteArray prefix(indent, ' ');
- d->loaderState.logger().qbsLog(LoggerInfo, true)
- << prefix
- << Tr::tr("Handling products took %1.")
- .arg(elapsedTimeString(d->elapsedTime));
- d->groupsHandler.printProfilingInfo(indent + 2);
-}
-
-ProductsHandler::~ProductsHandler() = default;
-
-void ProductsHandler::Private::handleNextProduct()
-{
- TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
-
- auto [product, queueSizeOnInsert] = topLevelProject.productsToHandle.front();
- topLevelProject.productsToHandle.pop_front();
-
- // If the queue of in-progress 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 = queueSizeOnInsert == -1
- || queueSizeOnInsert > int(topLevelProject.productsToHandle.size())
- ? Deferral::Allowed : Deferral::NotAllowed;
-
- loaderState.itemReader().setExtraSearchPathsStack(product->project->searchPathsStack);
- try {
- handleProduct(*product, deferral);
- if (product->name.startsWith(StringConstants::shadowProductPrefix()))
- topLevelProject.probes << product->info.probes;
- } catch (const ErrorInfo &err) {
- product->handleError(err);
- }
-
- // The search paths stack can change during dependency resolution (due to module providers);
- // check that we've rolled back all the changes
- QBS_CHECK(loaderState.itemReader().extraSearchPathsStack() == product->project->searchPathsStack);
-
- // If we encountered a dependency to an in-progress product or to a bulk dependency,
- // we defer handling this product if it hasn't failed yet and there is still forward progress.
- if (!product->info.delayedError.hasError() && !product->dependenciesResolved) {
- topLevelProject.productsToHandle.emplace_back(
- product, int(topLevelProject.productsToHandle.size()));
- }
-}
-
-void ProductsHandler::Private::handleProduct(ProductContext &product, Deferral deferral)
-{
- TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
- topLevelProject.checkCancelation(loaderState.parameters());
-
- if (product.info.delayedError.hasError())
- return;
-
- product.dependenciesResolved = loaderState.dependenciesResolver()
- .resolveDependencies(product, deferral);
- if (!product.dependenciesResolved)
- return;
-
- // Run probes for modules and product.
- resolveProbes(product);
-
- // After the probes have run, we can switch on the evaluator cache.
- Evaluator &evaluator = loaderState.evaluator();
- FileTags fileTags = evaluator.fileTagsValue(product.item, StringConstants::typeProperty());
- EvalCacheEnabler cacheEnabler(&evaluator, evaluator.stringValue(
- product.item,
- StringConstants::sourceDirectoryProperty()));
-
- // Run module validation scripts.
- for (const Item::Module &module : product.item->modules()) {
- if (!validateModule(product, 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 = product.item->modules().rbegin(); it != product.item->modules().rend(); ++it)
- updateModulePresentState(product, *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.
- loaderState.propertyMerger().doFinalMerge(product.item);
-
- const bool enabled = topLevelProject.checkItemCondition(product.item, evaluator);
- loaderState.dependenciesResolver().checkDependencyParameterDeclarations(
- product.item, product.name);
-
- handleGroups(product);
-
- // Collect the full list of fileTags, including the values contributed by modules.
- if (!product.info.delayedError.hasError() && enabled
- && !product.name.startsWith(StringConstants::shadowProductPrefix())) {
- for (const FileTag &tag : fileTags)
- topLevelProject.productsByType.insert({tag, &product});
- product.item->setProperty(StringConstants::typeProperty(),
- VariantValue::create(sorted(fileTags.toStringList())));
- }
- topLevelProject.productInfos[product.item] = product.info;
-}
-
-void ProductsHandler::Private::resolveProbes(ProductContext &product)
-{
- for (const Item::Module &module : product.item->modules()) {
- runModuleProbes(product, module);
- if (product.info.delayedError.hasError())
- return;
- }
- resolveProbes(product, product.item);
-}
-
-void ProductsHandler::Private::resolveProbes(ProductContext &product, Item *item)
-{
- product.info.probes << loaderState.probesResolver().resolveProbes(
- {product.name, product.uniqueName()}, item);
-}
-
-void ProductsHandler::Private::handleModuleSetupError(
- ProductContext &product, const Item::Module &module, const ErrorInfo &error)
-{
- if (module.required) {
- product.handleError(error);
- } else {
- qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString()
- << "found, but not usable in product" << product.name
- << error.toString();
- createNonPresentModule(*module.item->pool(), module.name.toString(),
- QStringLiteral("failed validation"), module.item);
- }
-}
-
-void ProductsHandler::Private::runModuleProbes(ProductContext &product, const Item::Module &module)
-{
- if (!module.item->isPresentModule())
- return;
- if (module.productInfo && loaderState.topLevelProject().disabledItems.contains(
- module.productInfo->item)) {
- createNonPresentModule(*module.item->pool(), module.name.toString(),
- QLatin1String("module's exporting product is disabled"),
- module.item);
- return;
- }
- try {
- resolveProbes(product, 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(
- 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(product, module, error);
- }
-}
-
-bool ProductsHandler::Private::validateModule(ProductContext &product, const Item::Module &module)
-{
- if (!module.item->isPresentModule())
- return true;
- try {
- 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(product, module, error);
- if (product.info.delayedError.hasError())
- return false;
- }
- return true;
-}
-
-void ProductsHandler::Private::updateModulePresentState(ProductContext &product,
- const Item::Module &module)
-{
- if (!module.item->isPresentModule())
- return;
- bool hasPresentLoadingItem = false;
- for (const Item * const loadingItem : module.loadingItems) {
- if (loadingItem == 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 (loaderState.topLevelProject().disabledItems.contains(
- loadingItem->prototype()->parent())) {
- continue;
- }
- }
- hasPresentLoadingItem = true;
- break;
- }
- if (!hasPresentLoadingItem) {
- createNonPresentModule(*module.item->pool(), module.name.toString(),
- QLatin1String("imported only by disabled module(s)"),
- module.item);
- }
-}
-
-void ProductsHandler::Private::handleGroups(ProductContext &product)
-{
- groupsHandler.setupGroups(product.item, product.scope);
- product.info.modulePropertiesSetInGroups = groupsHandler.modulePropertiesSetInGroups();
- loaderState.topLevelProject().disabledItems.unite(groupsHandler.disabledGroups());
-}
-
-} // 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
index d771e632c..be99d547d 100644
--- a/src/lib/corelib/loader/projectresolver.cpp
+++ b/src/lib/corelib/loader/projectresolver.cpp
@@ -39,7 +39,10 @@
#include "projectresolver.h"
-#include "projecttreebuilder.h"
+#include "itemreader.h"
+#include "loaderutils.h"
+#include "productscollector.h"
+#include "productsresolver.h"
#include <jsextensions/jsextensions.h>
#include <jsextensions/moduleproperties.h>
@@ -87,169 +90,59 @@
namespace qbs {
namespace Internal {
-extern bool debugProperties;
-
-static const FileTag unknownFileTag()
-{
- static const FileTag tag("unknown-file-tag");
- return tag;
-}
-
-struct ResolverProjectContext
+static SetupProjectParameters finalizedProjectParameters(const SetupProjectParameters &in,
+ Logger &logger)
{
- ResolverProjectContext *parentContext = nullptr;
- ResolvedProjectPtr project;
- std::vector<FileTaggerConstPtr> fileTaggers;
- std::vector<RulePtr> rules;
- JobLimits jobLimits;
- ResolvedModulePtr dummyModule;
-};
+ 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();
-using FileLocations = QHash<std::pair<QString, QString>, CodeLocation>;
-struct ResolverProductContext
-{
- ResolvedProductPtr product;
- QString buildDirectory;
- Item *item = nullptr;
- using ArtifactPropertiesInfo = std::pair<ArtifactPropertiesPtr, std::vector<CodeLocation>>;
- QHash<QStringList, ArtifactPropertiesInfo> artifactPropertiesPerFilter;
- FileLocations sourceArtifactLocations;
- GroupConstPtr currentGroup;
-};
+ QBS_CHECK(QFileInfo(params.projectFilePath()).isAbsolute());
+ QBS_CHECK(FileInfo::isAbsolute(params.buildRoot()));
-struct ModuleContext
-{
- ResolvedModulePtr module;
- JobLimits jobLimits;
-};
-
-class CancelException { };
+ return params;
+}
class ProjectResolver::Private
{
public:
- Private(ScriptEngine *engine, Logger &&logger) : engine(engine), logger(std::move(logger)) {}
+ Private(const SetupProjectParameters &parameters, ScriptEngine *engine, Logger &&logger)
+ : setupParams(finalizedProjectParameters(parameters, logger)), engine(engine),
+ logger(std::move(logger)) {}
- static void applyFileTaggers(const SourceArtifactPtr &artifact,
- const ResolvedProductConstPtr &product);
- static SourceArtifactPtr createSourceArtifact(
- const ResolvedProductPtr &rproduct, const QString &fileName, const GroupPtr &group,
- bool wildcard, const CodeLocation &filesLocation = CodeLocation(),
- FileLocations *fileLocations = nullptr, ErrorInfo *errorInfo = nullptr);
- void finalizeProjectParameters();
- 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, ResolverProjectContext *projectContext);
TopLevelProjectPtr resolveTopLevelProject();
- void resolveProject(Item *item, ResolverProjectContext *projectContext);
- void resolveProjectFully(Item *item, ResolverProjectContext *projectContext);
- void resolveSubProject(Item *item, ResolverProjectContext *projectContext);
- void resolveProduct(Item *item, ResolverProjectContext *projectContext);
- void resolveProductFully(Item *item, ResolverProjectContext *projectContext);
- void resolveModules(const Item *item, ResolverProjectContext *projectContext);
- void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
- const QVariantMap &parameters, JobLimits &jobLimits,
- ResolverProjectContext *projectContext);
- void gatherProductTypes(ResolvedProduct *product, Item *item);
- QVariantMap resolveAdditionalModuleProperties(const Item *group,
- const QVariantMap &currentValues);
- void resolveGroup(Item *item, ResolverProjectContext *projectContext);
- void resolveGroupFully(Item *item, ResolverProjectContext *projectContext, bool isEnabled);
- void resolveShadowProduct(Item *item, ResolverProjectContext *);
- void resolveExport(Item *exportItem, ResolverProjectContext *);
- std::unique_ptr<ExportedItem> resolveExportChild(const Item *item,
- const ExportedModule &module);
- void resolveRule(Item *item, ResolverProjectContext *projectContext);
- void resolveRuleArtifact(const RulePtr &rule, Item *item);
- void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item,
- const QStringList &namePrefix,
- QualifiedIdSet *seenBindings);
- void resolveFileTagger(Item *item, ResolverProjectContext *projectContext);
- void resolveJobLimit(Item *item, ResolverProjectContext *projectContext);
- void resolveScanner(Item *item, ResolverProjectContext *projectContext);
- void resolveProductDependencies();
- void postProcess(const ResolvedProductPtr &product, ResolverProjectContext *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 checkAllowedValues(
- const QVariant &v, const CodeLocation &loc, const PropertyDeclaration &decl,
- const QString &key) const;
- void createProductConfig(ResolvedProduct *product);
- ResolverProjectContext createProjectContext(ResolverProjectContext *parentProjectContext) const;
- void adaptExportedPropertyValues(const Item *shadowProductItem);
- void collectExportedProductDependencies();
+ 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();
- struct ProductDependencyInfo
- {
- ProductDependencyInfo(ResolvedProductPtr product,
- QVariantMap parameters = QVariantMap())
- : product(std::move(product)), parameters(std::move(parameters))
- {
- }
-
- ResolvedProductPtr product;
- QVariantMap parameters;
- };
-
- 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);
-
- using ItemFuncPtr = void (ProjectResolver::Private::*)(Item *item,
- ResolverProjectContext *projectContext);
- using ItemFuncMap = QMap<ItemType, ItemFuncPtr>;
- void callItemFunction(const ItemFuncMap &mappings, Item *item, ResolverProjectContext *projectContext);
-
+ const SetupProjectParameters setupParams;
ScriptEngine * const engine;
mutable Logger logger;
- Evaluator evaluator{engine};
ItemPool itemPool;
- SetupProjectParameters setupParams;
- ProjectTreeBuilder::Result loadResult;
- ProgressObserver *progressObserver = nullptr;
- ResolverProductContext *productContext = nullptr;
- ModuleContext *moduleContext = nullptr;
- QHash<Item *, ResolvedProductPtr> productsByItem;
- mutable QHash<FileContextConstPtr, ResolvedFileContextPtr> fileContextMap;
- mutable QHash<CodeLocation, ScriptFunctionPtr> scriptFunctionMap;
- mutable QHash<std::pair<QStringView, QStringList>, QString> scriptFunctions;
- mutable QHash<QStringView, QString> sourceCode;
- Set<CodeLocation> groupLocationWarnings;
- std::vector<std::pair<ResolvedProductPtr, Item *>> productExportInfo;
- std::vector<ErrorInfo> queuedErrors;
- std::vector<ProbeConstPtr> oldProjectProbes;
- QHash<QString, std::vector<ProbeConstPtr>> oldProductProbes;
- StoredModuleProviderInfo storedModuleProviderInfo;
- QVariantMap storedProfiles;
- FileTime lastResolveTime;
- qint64 elapsedTimeModPropEval = 0;
- qint64 elapsedTimeAllPropEval = 0;
- qint64 elapsedTimeGroups = 0;
+ TopLevelProjectContext topLevelProject;
+ LoaderState state{setupParams, topLevelProject, itemPool, *engine, logger};
+ Item *rootProjectItem = nullptr;
};
-
-ProjectResolver::ProjectResolver(ScriptEngine *engine, Logger logger)
- : d(makePimpl<Private>(engine, std::move(logger)))
+ProjectResolver::ProjectResolver(const SetupProjectParameters &parameters, ScriptEngine *engine,
+ Logger logger)
+ : d(makePimpl<Private>(parameters, engine, std::move(logger)))
{
d->logger.storeWarnings();
}
@@ -258,33 +151,33 @@ ProjectResolver::~ProjectResolver() = default;
void ProjectResolver::setProgressObserver(ProgressObserver *observer)
{
- d->progressObserver = observer;
+ d->state.topLevelProject().setProgressObserver(observer);
}
void ProjectResolver::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
{
- d->oldProjectProbes = oldProbes;
+ d->state.topLevelProject().setOldProjectProbes(oldProbes);
}
void ProjectResolver::setOldProductProbes(
const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
{
- d->oldProductProbes = oldProbes;
+ d->state.topLevelProject().setOldProductProbes(oldProbes);
}
void ProjectResolver::setLastResolveTime(const FileTime &time)
{
- d->lastResolveTime = time;
+ d->state.topLevelProject().setLastResolveTime(time);
}
void ProjectResolver::setStoredProfiles(const QVariantMap &profiles)
{
- d->storedProfiles = profiles;
+ d->state.topLevelProject().setProfileConfigs(profiles);
}
void ProjectResolver::setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo)
{
- d->storedModuleProviderInfo = providerInfo;
+ d->state.topLevelProject().setModuleProvidersCache(providerInfo.providers);
}
static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project)
@@ -306,12 +199,8 @@ static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project
}
}
-TopLevelProjectPtr ProjectResolver::resolve(const SetupProjectParameters &parameters)
+TopLevelProjectPtr ProjectResolver::resolve()
{
- d->setupParams = parameters;
- d->finalizeProjectParameters();
- QBS_CHECK(FileInfo::isAbsolute(d->setupParams.buildRoot()));
-
qCDebug(lcProjectResolver) << "resolving" << d->setupParams.projectFilePath();
d->engine->setEnvironment(d->setupParams.adjustedEnvironment());
@@ -326,23 +215,18 @@ TopLevelProjectPtr ProjectResolver::resolve(const SetupProjectParameters &parame
// 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 (d->progressObserver) {
- d->progressObserver->initialize(Tr::tr("Resolving project for configuration %1")
+ if (ProgressObserver * const observer = d->state.topLevelProject().progressObserver()) {
+ observer->initialize(Tr::tr("Resolving project for configuration %1")
.arg(TopLevelProject::deriveId(d->setupParams.finalBuildConfigurationTree())), 1);
- d->progressObserver->setScriptEngine(d->engine);
+ observer->addScriptEngine(d->engine);
}
const FileTime resolveTime = FileTime::currentTime();
TopLevelProjectPtr tlp;
- ProjectTreeBuilder projectTreeBuilder(d->setupParams, d->itemPool, d->evaluator, d->logger);
- projectTreeBuilder.setProgressObserver(d->progressObserver);
- projectTreeBuilder.setOldProjectProbes(d->oldProjectProbes);
- projectTreeBuilder.setOldProductProbes(d->oldProductProbes);
- projectTreeBuilder.setLastResolveTime(d->lastResolveTime);
- projectTreeBuilder.setStoredProfiles(d->storedProfiles);
- projectTreeBuilder.setStoredModuleProviderInfo(d->storedModuleProviderInfo);
- d->loadResult = projectTreeBuilder.load();
try {
+ d->checkOverriddenValues();
+ d->loadTopLevelProjectItem();
+ d->buildProjectTree();
tlp = d->resolveTopLevelProject();
} catch (const CancelException &) {
throw ErrorInfo(Tr::tr("Project resolving canceled for configuration '%1'.")
@@ -353,48 +237,13 @@ TopLevelProjectPtr ProjectResolver::resolve(const SetupProjectParameters &parame
tlp->lastEndResolveTime = FileTime::currentTime();
// E.g. if the top-level project is disabled.
- if (d->progressObserver) {
- d->progressObserver->setFinished();
+ if (ProgressObserver * const observer = d->state.topLevelProject().progressObserver()) {
+ observer->setFinished();
d->printProfilingInfo();
}
return tlp;
}
-void ProjectResolver::Private::checkCancelation() const
-{
- if (progressObserver && progressObserver->canceled())
- throw CancelException();
-}
-
-QString ProjectResolver::Private::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::Private::verbatimValue(Item *item, const QString &name,
- bool *propertyWasSet) const
-{
- return verbatimValue(item->property(name), propertyWasSet);
-}
-
-void ProjectResolver::Private::ignoreItem(Item *item, ResolverProjectContext *projectContext)
-{
- Q_UNUSED(item);
- Q_UNUSED(projectContext);
-}
-
static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject)
{
Set<QString> subProjectNames;
@@ -420,65 +269,60 @@ static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject)
TopLevelProjectPtr ProjectResolver::Private::resolveTopLevelProject()
{
- if (progressObserver)
- progressObserver->setMaximum(int(loadResult.productInfos.size()));
+ 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()));
- project->buildSystemFiles = loadResult.qbsFiles;
- project->profileConfigs = loadResult.profileConfigs;
- project->probes = loadResult.projectProbes;
- project->moduleProviderInfo = loadResult.storedModuleProviderInfo;
- ResolverProjectContext projectContext;
- projectContext.project = project;
-
- resolveProject(loadResult.root, &projectContext);
+ 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;
- for (const ErrorInfo &e : queuedErrors)
- appendError(accumulatedErrors, e);
+ {
+ 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();
- project->canonicalFilePathResults = engine->canonicalFilePathResults();
- project->fileExistsResults = engine->fileExistsResults();
- project->directoryEntriesResults = engine->directoryEntriesResults();
- project->fileLastModifiedResults = engine->fileLastModifiedResults();
- project->environment = engine->environment();
- project->buildSystemFiles.unite(engine->imports());
+ state.topLevelProject().collectDataFromEngine(*engine);
makeSubProjectNamesUniqe(project);
- resolveProductDependencies();
- collectExportedProductDependencies();
checkForDuplicateProductNames(project);
+ project->warningsEncountered << logger.warnings();
- 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 = logger.warnings();
return project;
}
-void ProjectResolver::Private::resolveProject(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::resolveProject(ProjectContext *projectContext)
{
- checkCancelation();
+ 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();
- if (projectContext->parentContext)
- projectContext->project->enabled = projectContext->parentContext->project->enabled;
- projectContext->project->location = item->location();
try {
- resolveProjectFully(item, projectContext);
+ resolveProjectFully(projectContext);
} catch (const ErrorInfo &error) {
if (!projectContext->project->enabled) {
qCDebug(lcProjectResolver) << "error resolving project"
@@ -491,17 +335,20 @@ void ProjectResolver::Private::resolveProject(Item *item, ResolverProjectContext
}
}
-void ProjectResolver::Private::resolveProjectFully(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::resolveProjectFully(ProjectContext *projectContext)
{
+ Item * const item = projectContext->item;
projectContext->project->enabled = projectContext->project->enabled
- && evaluator.boolValue(item, StringConstants::conditionProperty());
- projectContext->project->name = evaluator.stringValue(item, StringConstants::nameProperty());
+ && 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(),
- evaluator.stringValue(item, StringConstants::profileProperty()));
+ state.evaluator().stringValue(
+ item, StringConstants::profileProperty()));
projectContext->project->setProjectProperties(projectProperties);
return;
}
@@ -515,1484 +362,200 @@ void ProjectResolver::Private::resolveProjectFully(Item *item, ResolverProjectCo
continue;
const ValueConstPtr v = item->property(it.key());
QBS_ASSERT(v && v->type() != Value::ItemValueType, continue);
- const ScopedJsValue sv(engine->context(), evaluator.value(item, it.key()));
+ const ScopedJsValue sv(engine->context(), state.evaluator().value(item, it.key()));
projectProperties.insert(it.key(), getJsVariant(engine->context(), sv));
}
projectContext->project->setProjectProperties(projectProperties);
- static const ItemFuncMap mapping = {
- { ItemType::Project, &ProjectResolver::Private::resolveProject },
- { ItemType::SubProject, &ProjectResolver::Private::resolveSubProject },
- { ItemType::Product, &ProjectResolver::Private::resolveProduct },
- { ItemType::Probe, &ProjectResolver::Private::ignoreItem },
- { ItemType::FileTagger, &ProjectResolver::Private::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::Private::resolveJobLimit },
- { ItemType::Rule, &ProjectResolver::Private::resolveRule },
- { ItemType::PropertyOptions, &ProjectResolver::Private::ignoreItem }
- };
-
for (Item * const child : item->children()) {
+ state.topLevelProject().checkCancelation();
try {
- callItemFunction(mapping, child, projectContext);
+ 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) {
- queuedErrors.push_back(e);
+ state.topLevelProject().addQueuedError(e);
}
}
- for (const ResolvedProductPtr &product : projectContext->project->products)
- postProcess(product, projectContext);
+ for (ProjectContext * const childContext : projectContext->children)
+ resolveProject(childContext);
}
-void ProjectResolver::Private::resolveSubProject(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::resolveSubProject(Item *item, ProjectContext *projectContext)
{
- ResolverProjectContext subProjectContext = createProjectContext(projectContext);
-
- Item * const projectItem = item->child(ItemType::Project);
- if (projectItem) {
- resolveProject(projectItem, &subProjectContext);
+ // 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;
- }
- // 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 = evaluator.stringValue(
- propertiesItem, StringConstants::nameProperty());
+ 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::resolveProduct(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::checkOverriddenValues()
{
- checkCancelation();
- evaluator.clearPropertyDependencies();
- ResolverProductContext 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();
- class ProductContextSwitcher {
- public:
- ProductContextSwitcher(ProjectResolver::Private &resolver, ResolverProductContext &newContext,
- ProgressObserver *progressObserver)
- : m_resolver(resolver), m_progressObserver(progressObserver) {
- QBS_CHECK(!m_resolver.productContext);
- m_resolver.productContext = &newContext;
- }
- ~ProductContextSwitcher() {
- if (m_progressObserver)
- m_progressObserver->incrementProgressValue();
- m_resolver.productContext = nullptr;
- }
- private:
- ProjectResolver::Private &m_resolver;
- ProgressObserver * const m_progressObserver;
- } contextSwitcher(*this, productContext, progressObserver);
- const auto errorFromDelayedError = [&] {
- ProductInfo &pi = 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();
- return errorInfo;
- }
- return ErrorInfo();
+ 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('.')); });
};
-
- // 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(item, projectContext);
- 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, item->location());
- appendError(fullError, e);
- if (!product->enabled) {
- qCDebug(lcProjectResolver) << fullError.toString();
- return;
- }
- if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw fullError;
- logger.printWarning(fullError);
- logger.printWarning(ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.")
- .arg(product->name), item->location()));
- product->enabled = false;
- }
-}
-
-void ProjectResolver::Private::resolveProductFully(Item *item, ResolverProjectContext *projectContext)
-{
- const ResolvedProductPtr product = productContext->product;
- projectContext->project->products.push_back(product);
- product->name = evaluator.stringValue(item, StringConstants::nameProperty());
-
- // product->buildDirectory() isn't valid yet, because the productProperties map is not ready.
- productContext->buildDirectory
- = evaluator.stringValue(item, StringConstants::buildDirectoryProperty());
- product->multiplexConfigurationId
- = evaluator.stringValue(item, StringConstants::multiplexConfigurationIdProperty());
- qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName();
- productsByItem.insert(item, product);
- product->enabled = product->enabled
- && evaluator.boolValue(item, StringConstants::conditionProperty());
- ProductInfo &pi = loadResult.productInfos[item];
- gatherProductTypes(product.get(), item);
- 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 = 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(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(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(setupParams.deprecationWarningMode(), logger);
- subItems.prepend(fakeGroup);
- }
-
- static const ItemFuncMap mapping = {
- { ItemType::Depends, &ProjectResolver::Private::ignoreItem },
- { ItemType::Rule, &ProjectResolver::Private::resolveRule },
- { ItemType::FileTagger, &ProjectResolver::Private::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::Private::resolveJobLimit },
- { ItemType::Group, &ProjectResolver::Private::resolveGroup },
- { ItemType::Product, &ProjectResolver::Private::resolveShadowProduct },
- { ItemType::Export, &ProjectResolver::Private::resolveExport },
- { ItemType::Probe, &ProjectResolver::Private::ignoreItem },
- { ItemType::PropertyOptions, &ProjectResolver::Private::ignoreItem }
- };
-
- for (Item * const child : std::as_const(subItems))
- callItemFunction(mapping, child, projectContext);
-
- for (const ResolverProjectContext *p = projectContext; p; p = p->parentContext) {
- JobLimits tempLimits = p->jobLimits;
- product->jobLimits = tempLimits.update(product->jobLimits);
- }
-
- resolveModules(item, projectContext);
-}
-
-void ProjectResolver::Private::resolveModules(const Item *item, ResolverProjectContext *projectContext)
-{
- JobLimits jobLimits;
- for (const Item::Module &m : item->modules()) {
- resolveModule(m.name, m.item, m.productInfo.has_value(), m.parameters, jobLimits,
- projectContext);
- }
- for (int i = 0; i < jobLimits.count(); ++i) {
- const JobLimit &moduleJobLimit = jobLimits.jobLimitAt(i);
- if (productContext->product->jobLimits.getLimit(moduleJobLimit.pool()) == -1)
- productContext->product->jobLimits.setJobLimit(moduleJobLimit);
- }
-}
-
-void ProjectResolver::Private::resolveModule(
- const QualifiedId &moduleName, Item *item, bool isProduct, const QVariantMap &parameters,
- JobLimits &jobLimits, ResolverProjectContext *projectContext)
-{
- checkCancelation();
- if (!item->isPresentModule())
- return;
-
- ModuleContext * const oldModuleContext = moduleContext;
- ModuleContext newModuleContext;
- newModuleContext.module = ResolvedModule::create();
- moduleContext = &newModuleContext;
-
- const ResolvedModulePtr &module = newModuleContext.module;
- module->name = moduleName.toString();
- module->isProduct = isProduct;
- module->product = 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();
- }
-
- productContext->product->modules.push_back(module);
- if (!parameters.empty())
- productContext->product->moduleParameters[module] = parameters;
-
- static const ItemFuncMap mapping {
- { ItemType::Group, &ProjectResolver::Private::ignoreItem },
- { ItemType::Rule, &ProjectResolver::Private::resolveRule },
- { ItemType::FileTagger, &ProjectResolver::Private::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::Private::resolveJobLimit },
- { ItemType::Scanner, &ProjectResolver::Private::resolveScanner },
- { ItemType::PropertyOptions, &ProjectResolver::Private::ignoreItem },
- { ItemType::Depends, &ProjectResolver::Private::ignoreItem },
- { ItemType::Parameter, &ProjectResolver::Private::ignoreItem },
- { ItemType::Properties, &ProjectResolver::Private::ignoreItem },
- { ItemType::Probe, &ProjectResolver::Private::ignoreItem }
- };
- for (Item *child : item->children())
- callItemFunction(mapping, child, projectContext);
- for (int i = 0; i < newModuleContext.jobLimits.count(); ++i) {
- const JobLimit &newJobLimit = newModuleContext.jobLimits.jobLimitAt(i);
- const int oldLimit = jobLimits.getLimit(newJobLimit.pool());
- if (oldLimit == -1 || oldLimit > newJobLimit.limit())
- jobLimits.setJobLimit(newJobLimit);
- }
-
- moduleContext = oldModuleContext; // FIXME: Exception safety
-}
-
-void ProjectResolver::Private::gatherProductTypes(ResolvedProduct *product, Item *item)
-{
- const VariantValuePtr type = item->variantProperty(StringConstants::typeProperty());
- if (type)
- product->fileTags = FileTags::fromStringList(type->value().toStringList());
-}
-
-SourceArtifactPtr ProjectResolver::Private::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;
-}
-
-void ProjectResolver::Private::finalizeProjectParameters()
-{
- if (setupParams.topLevelProfile().isEmpty()) {
- Settings settings(setupParams.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();
- }
- setupParams.setTopLevelProfile(profileName);
- setupParams.expandBuildConfiguration();
- }
- setupParams.finalizeProjectFilePath();
- QBS_CHECK(QFileInfo(setupParams.projectFilePath()).isAbsolute());
-}
-
-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)
+ const QVariantMap &overriddenValues = state.parameters().overriddenValues();
+ for (auto it = overriddenValues.begin(); it != overriddenValues.end(); ++it) {
+ if (matchesPrefix(it.key())) {
+ collectNameFromOverride(it.key());
continue;
- transform(deps.value(prop), remainingProps, [](const QualifiedId &id) { return id; });
- }
- return allProperties;
-}
-
-QVariantMap ProjectResolver::Private::resolveAdditionalModuleProperties(
- const Item *group, const QVariantMap &currentValues)
-{
- // Step 1: Retrieve the properties directly set in the group
- const ModulePropertiesPerGroup &mp = mapValue(
- loadResult.productInfos, 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(
- rangeTo<std::deque<QualifiedId>>(propsSetInGroup),
- 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(&evaluator, 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 : std::as_const(propsForModule))
- reusableValues.remove(prop);
- modulesMap.insert(fullModName,
- evaluateProperties(module.item, module.item, reusableValues, true, true));
- }
- return modulesMap;
-}
-
-void ProjectResolver::Private::resolveGroup(Item *item, ResolverProjectContext *projectContext)
-{
- checkCancelation();
- const bool parentEnabled = productContext->currentGroup
- ? productContext->currentGroup->enabled
- : productContext->product->enabled;
- const bool isEnabled = parentEnabled
- && 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 (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw;
- logger.printWarning(error);
- }
-}
-
-void ProjectResolver::Private::resolveGroupFully(
- Item *item, ResolverProjectContext *projectContext, bool isEnabled)
-{
- AccumulatingTimer groupTimer(setupParams.logElapsedTime() ? &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 = productContext->currentGroup
- ? productContext->currentGroup->properties
- : 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 = 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;
-
- ResolverProductContext::ArtifactPropertiesInfo &apinfo
- = 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));
- 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;
+ 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());
}
- 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 && productContext->currentGroup)
- group->prefix = 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 = evaluator.boolValue(item, StringConstants::overrideTagsProperty());
- if (group->overrideTags && fileTagsSet) {
- if (group->fileTags.empty() )
- group->fileTags.insert(unknownFileTag());
- } else if (productContext->currentGroup) {
- group->fileTags.unite(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 = 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(productContext->product, fileName, group, true, filesLocation,
- &productContext->sourceArtifactLocations, &fileError);
- }
-
- for (const QString &fileName : std::as_const(files)) {
- createSourceArtifact(productContext->product, fileName, group, false, filesLocation,
- &productContext->sourceArtifactLocations, &fileError);
- }
- if (fileError.hasError()) {
- if (group->enabled) {
- if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw ErrorInfo(fileError);
- 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(productContext->product->groups.size());
- productContext->product->groups.push_back(group);
-
- class GroupContextSwitcher {
- public:
- GroupContextSwitcher(ResolverProductContext &context, const GroupConstPtr &newGroup)
- : m_context(context), m_oldGroup(context.currentGroup) {
- m_context.currentGroup = newGroup;
- }
- ~GroupContextSwitcher() { m_context.currentGroup = m_oldGroup; }
- private:
- ResolverProductContext &m_context;
- const GroupConstPtr m_oldGroup;
- };
- GroupContextSwitcher groupSwitcher(*productContext, group);
- for (Item * const childItem : item->children())
- resolveGroup(childItem, projectContext);
}
-void ProjectResolver::Private::adaptExportedPropertyValues(const Item *shadowProductItem)
+void ProjectResolver::Private::collectNameFromOverride(const QString &overrideString)
{
- ExportedModule &m = productContext->product->exportedModule;
- const QVariantList prefixList = m.propertyValues.take(
- StringConstants::prefixMappingProperty()).toList();
- const QString shadowProductName = evaluator.stringValue(
- shadowProductItem, StringConstants::nameProperty());
- const QString shadowProductBuildDir = 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);
+ 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);
};
- 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 ProjectResolver::Private::collectExportedProductDependencies()
-{
- ResolvedProductPtr dummyProduct = ResolvedProduct::create();
- dummyProduct->enabled = false;
- for (const auto &exportingProductInfo : std::as_const(productExportInfo)) {
- const ResolvedProductPtr exportingProduct = exportingProductInfo.first;
- if (!exportingProduct->enabled)
- continue;
- Item * const importingProductItem = exportingProductInfo.second;
-
- 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.productInfo) {
- directDeps.emplace_back(productsByItem.value(dep.productInfo->item),
- 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());
- }
-}
-
-void ProjectResolver::Private::resolveShadowProduct(Item *item, ResolverProjectContext *)
-{
- if (!productContext->product->enabled)
+ const QString &projectName = extract(StringConstants::projectsOverridePrefix());
+ if (!projectName.isEmpty()) {
+ state.topLevelProject().addProjectNameUsedInOverrides(projectName);
return;
- for (const auto &m : item->modules()) {
- if (m.name.toString() != productContext->product->name)
- continue;
- collectPropertiesForExportItem(m.item);
- for (const auto &dep : m.item->modules())
- collectPropertiesForModuleInExportItem(dep);
- break;
- }
- try {
- adaptExportedPropertyValues(item);
- } catch (const ErrorInfo &) {}
- productExportInfo.emplace_back(productContext->product, item);
-}
-
-void ProjectResolver::Private::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 || 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 QRegularExpression &regex)
-{
- return prop.sourceCode.indexOf(regex) != -1;
-}
-
-static bool usesImport(const ExportedItem &item, const QRegularExpression &regex)
-{
- 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); });
-}
-
-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::Private::resolveExport(Item *exportItem, ResolverProjectContext *)
-{
- ExportedModule &exportedModule = 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);
-
- 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> ProjectResolver::Private::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;
-}
-
-QString ProjectResolver::Private::sourceCodeAsFunction(const JSSourceValueConstPtr &value,
- const PropertyDeclaration &decl) const
-{
- QString &scriptFunction = 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::Private::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const
-{
- QString &code = sourceCode[value->sourceCode()];
- if (!code.isNull())
- return code;
- code = value->sourceCodeForEvaluation();
- return code;
-}
-
-ScriptFunctionPtr ProjectResolver::Private::scriptFunctionValue(
- Item *item, const QString &name) const
-{
- JSSourceValuePtr value = item->sourceProperty(name);
- ScriptFunctionPtr &script = 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::Private::resolvedFileContext(
- const FileContextConstPtr &ctx) const
-{
- ResolvedFileContextPtr &result = fileContextMap[ctx];
- if (!result)
- result = ResolvedFileContext::create(*ctx);
- return result;
-}
-
-void ProjectResolver::Private::resolveRule(Item *item, ResolverProjectContext *projectContext)
-{
- checkCancelation();
-
- if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
+ const QString &productName = extract(StringConstants::productsOverridePrefix());
+ if (!productName.isEmpty()) {
+ state.topLevelProject().addProductNameUsedInOverrides(productName.left(
+ productName.indexOf(StringConstants::dot())));
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 = evaluator.stringValue(item, StringConstants::nameProperty());
- rule->prepareScript.initialize(
- scriptFunctionValue(item, StringConstants::prepareProperty()));
- rule->outputArtifactsScript.initialize(
- 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 ProjectResolver::Private::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 = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty());
- artifact->alwaysUpdated = 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::Private::resolveRuleArtifactBinding(
- const RuleArtifactPtr &ruleArtifact, Item *item, const QStringList &namePrefix,
- QualifiedIdSet *seenBindings)
+void ProjectResolver::Private::loadTopLevelProjectItem()
{
- 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::Private::resolveFileTagger(Item *item, ResolverProjectContext *projectContext)
-{
- checkCancelation();
- if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
+ 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;
- 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 ProjectResolver::Private::resolveJobLimit(Item *item, ResolverProjectContext *projectContext)
-{
- 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());
+ 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());
}
- 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);
}
-void ProjectResolver::Private::resolveScanner(Item *item, ResolverProjectContext *projectContext)
+void ProjectResolver::Private::buildProjectTree()
{
- checkCancelation();
- if (!evaluator.boolValue(item, StringConstants::conditionProperty())) {
- qCDebug(lcProjectResolver) << "scanner condition is false";
- return;
- }
+ 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);
- ResolvedScannerPtr scanner = ResolvedScanner::create();
- scanner->module = moduleContext ? moduleContext->module : projectContext->dummyModule;
- scanner->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty());
- scanner->recursive = evaluator.boolValue(item, StringConstants::recursiveProperty());
- scanner->searchPathsScript.initialize(
- scriptFunctionValue(item, StringConstants::searchPathsProperty()));
- scanner->scanScript.initialize(
- scriptFunctionValue(item, StringConstants::scanProperty()));
- productContext->product->scanners.push_back(scanner);
-}
-
-void ProjectResolver::Private::matchArtifactProperties(const ResolvedProductPtr &product,
- const std::vector<SourceArtifactPtr> &artifacts)
-{
- for (const SourceArtifactPtr &artifact : artifacts) {
- for (const auto &artifactProperties : product->artifactProperties) {
- if (!artifact->isTargetOfModule()
- && artifact->fileTags.intersects(artifactProperties->fileTagsFilter())) {
- artifact->properties = artifactProperties->propertyMap();
- }
- }
- }
+ AccumulatingTimer timer(state.parameters().logElapsedTime()
+ ? &state.topLevelProject().timingData().propertyChecking : nullptr);
+ checkPropertyDeclarations(rootProjectItem, state);
}
void ProjectResolver::Private::printProfilingInfo()
{
if (!setupParams.logElapsedTime())
return;
- logger.qbsLog(LoggerInfo, true)
- << " " << Tr::tr("All property evaluation took %1.")
- .arg(elapsedTimeString(elapsedTimeAllPropEval));
- logger.qbsLog(LoggerInfo, true)
- << " " << Tr::tr("Module property evaluation took %1.")
- .arg(elapsedTimeString(elapsedTimeModPropEval));
- logger.qbsLog(LoggerInfo, true)
- << " " << Tr::tr("Resolving groups (without module property "
- "evaluation) took %1.")
- .arg(elapsedTimeString(elapsedTimeGroups));
-}
-
-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 ProjectResolver::Private::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 = 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 {
- TempScopeSetter tss(it.value(), productModuleInstance);
- evaluateProperty(exportItem, it.key(), it.value(), exportedModule.propertyValues,
- false);
- }
- }
-}
-
-// Collects module properties assigned to in other (higher-level) modules.
-void ProjectResolver::Private::collectPropertiesForModuleInExportItem(const Item::Module &module)
-{
- if (!module.item->isPresentModule())
- return;
- ExportedModule &exportedModule = productContext->product->exportedModule;
- if (module.productInfo || module.name.first() == StringConstants::qbsModule())
- return;
- const auto checkName = [module](const ExportedModuleDependency &d) {
- return module.name.toString() == d.name;
+ const auto print = [this](int indent, const QString &pattern, qint64 time) {
+ logger.qbsLog(LoggerInfo, true) << QByteArray(indent, ' ')
+ << pattern.arg(elapsedTimeString(time));
};
- 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;
- 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 ProjectResolver::Private::resolveProductDependencies()
-{
- for (auto it = productsByItem.cbegin(); it != productsByItem.cend(); ++it) {
- const ResolvedProductPtr &product = it.value();
- for (const Item::Module &module : it.key()->modules()) {
- if (!module.productInfo)
- continue;
- const ResolvedProductPtr &dep = productsByItem.value(module.productInfo->item);
- QBS_CHECK(dep);
- QBS_CHECK(dep != product);
- it.value()->dependencies << dep;
- it.value()->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 ProjectResolver::Private::postProcess(const ResolvedProductPtr &product,
- ResolverProjectContext *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::Private::applyFileTaggers(const ResolvedProductPtr &product) const
-{
- for (const SourceArtifactPtr &artifact : product->allEnabledFiles())
- applyFileTaggers(artifact, product);
-}
-
-void ProjectResolver::Private::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::Private::evaluateModuleValues(Item *item, bool lookupPrototype)
-{
- AccumulatingTimer modPropEvalTimer(setupParams.logElapsedTime()
- ? &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::Private::evaluateProperties(Item *item, bool lookupPrototype,
- bool checkErrors)
-{
- const QVariantMap tmplt;
- return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors);
-}
-
-QVariantMap ProjectResolver::Private::evaluateProperties(
- const Item *item, const Item *propertiesContainer, const QVariantMap &tmplt,
- bool lookupPrototype, bool checkErrors)
-{
- AccumulatingTimer propEvalTimer(setupParams.logElapsedTime()
- ? &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::Private::evaluateProperty(
- const Item *item, const QString &propName, const ValuePtr &propValue, QVariantMap &result,
- bool checkErrors)
-{
- JSContext * const ctx = 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, evaluator.property(item, propName));
- if (JsException ex = 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();
- }
- checkAllowedValues(v, propValue->location(), pd, propName);
- 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();
-
- checkAllowedValues(v, propValue->location(), pd, propName);
- result[propName] = v;
- break;
- }
- }
-}
-
-void ProjectResolver::Private::checkAllowedValues(
- const QVariant &value, const CodeLocation &loc, const PropertyDeclaration &decl,
- const QString &key) const
-{
- const auto type = decl.type();
- if (type != PropertyDeclaration::String && type != PropertyDeclaration::StringList)
- return;
-
- if (value.isNull())
- return;
-
- const auto &allowedValues = decl.allowedValues();
- if (allowedValues.isEmpty())
- return;
-
- const auto checkValue = [this, &loc, &allowedValues, &key](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, setupParams, 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());
- }
-}
-
-void ProjectResolver::Private::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->setProperty(StringConstants::nameProperty(), VariantValuePtr());
- }
- 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(), 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::Private::createProductConfig(ResolvedProduct *product)
-{
- EvalCacheEnabler cachingEnabler(&evaluator, productContext->product->sourceDirectory);
- product->moduleProperties->setValue(evaluateModuleValues(productContext->item));
- product->productProperties = evaluateProperties(productContext->item, productContext->item,
- QVariantMap(), true, true);
-}
-
-void ProjectResolver::Private::callItemFunction(const ItemFuncMap &mappings, Item *item,
- ResolverProjectContext *projectContext)
-{
- const ItemFuncPtr f = mappings.value(item->type());
- QBS_CHECK(f);
- if (item->type() == ItemType::Project) {
- ResolverProjectContext subProjectContext = createProjectContext(projectContext);
- (this->*f)(item, &subProjectContext);
- } else {
- (this->*f)(item, projectContext);
- }
-}
-
-ResolverProjectContext ProjectResolver::Private::createProjectContext(
- ResolverProjectContext *parentProjectContext) const
-{
- ResolverProjectContext subProjectContext;
- subProjectContext.parentContext = parentProjectContext;
- subProjectContext.project = ResolvedProject::create();
- parentProjectContext->project->subProjects.push_back(subProjectContext.project);
- subProjectContext.project->parentProject = parentProjectContext->project;
- return subProjectContext;
+ 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
diff --git a/src/lib/corelib/loader/projectresolver.h b/src/lib/corelib/loader/projectresolver.h
index 2b5a55066..22ad82518 100644
--- a/src/lib/corelib/loader/projectresolver.h
+++ b/src/lib/corelib/loader/projectresolver.h
@@ -62,7 +62,7 @@ class StoredModuleProviderInfo;
class QBS_AUTOTEST_EXPORT ProjectResolver
{
public:
- ProjectResolver(ScriptEngine *engine, Logger logger);
+ ProjectResolver(const SetupProjectParameters &parameters, ScriptEngine *engine, Logger logger);
~ProjectResolver();
void setProgressObserver(ProgressObserver *observer);
@@ -71,7 +71,7 @@ public:
void setLastResolveTime(const FileTime &time);
void setStoredProfiles(const QVariantMap &profiles);
void setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo);
- TopLevelProjectPtr resolve(const SetupProjectParameters &parameters);
+ TopLevelProjectPtr resolve();
private:
class Private;
diff --git a/src/lib/corelib/loader/projecttreebuilder.cpp b/src/lib/corelib/loader/projecttreebuilder.cpp
deleted file mode 100644
index ec3869895..000000000
--- a/src/lib/corelib/loader/projecttreebuilder.cpp
+++ /dev/null
@@ -1,297 +0,0 @@
-/****************************************************************************
-**
-** 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 "projecttreebuilder.h"
-
-#include "dependenciesresolver.h"
-#include "itemreader.h"
-#include "localprofiles.h"
-#include "moduleinstantiator.h"
-#include "modulepropertymerger.h"
-#include "probesresolver.h"
-#include "productitemmultiplexer.h"
-#include "productscollector.h"
-#include "productshandler.h"
-
-#include <language/builtindeclarations.h>
-#include <language/evaluator.h>
-#include <language/filecontext.h>
-#include <language/filetags.h>
-#include <language/item.h>
-#include <language/language.h>
-#include <language/scriptengine.h>
-#include <language/value.h>
-#include <logging/categories.h>
-#include <logging/translator.h>
-#include <tools/fileinfo.h>
-#include <tools/filetime.h>
-#include <tools/preferences.h>
-#include <tools/progressobserver.h>
-#include <tools/profile.h>
-#include <tools/profiling.h>
-#include <tools/scripttools.h>
-#include <tools/settings.h>
-#include <tools/setupprojectparameters.h>
-#include <tools/stringconstants.h>
-#include <tools/version.h>
-
-#include <QDir>
-#include <QDirIterator>
-#include <QFileInfo>
-
-#include <list>
-#include <memory>
-#include <optional>
-#include <queue>
-#include <stack>
-#include <utility>
-#include <vector>
-
-namespace qbs::Internal {
-
-class ProjectTreeBuilder::Private
-{
-public:
- Private(const SetupProjectParameters &parameters, ItemPool &itemPool, Evaluator &evaluator,
- Logger &logger) : state(parameters, itemPool, evaluator, logger) {}
-
- Item *loadTopLevelProjectItem();
- void checkOverriddenValues();
- void collectNameFromOverride(const QString &overrideString);
- void handleTopLevelProject(Item *projectItem);
- void printProfilingInfo();
-
- LoaderState state;
- ProductsCollector productsCollector{state};
- ProductsHandler productsHandler{state};
- FileTime lastResolveTime;
- QVariantMap storedProfiles;
-
- qint64 elapsedTimePropertyChecking = 0;
-};
-
-ProjectTreeBuilder::ProjectTreeBuilder(const SetupProjectParameters &parameters, ItemPool &itemPool,
- Evaluator &evaluator, Logger &logger)
- : d(makePimpl<Private>(parameters, itemPool, evaluator, logger)) {}
-ProjectTreeBuilder::~ProjectTreeBuilder() = default;
-
-void ProjectTreeBuilder::setProgressObserver(ProgressObserver *progressObserver)
-{
- d->state.topLevelProject().progressObserver = progressObserver;
-}
-
-void ProjectTreeBuilder::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
-{
- d->state.probesResolver().setOldProjectProbes(oldProbes);
-}
-
-void ProjectTreeBuilder::setOldProductProbes(
- const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
-{
- d->state.probesResolver().setOldProductProbes(oldProbes);
-}
-
-void ProjectTreeBuilder::setLastResolveTime(const FileTime &time) { d->lastResolveTime = time; }
-
-void ProjectTreeBuilder::setStoredProfiles(const QVariantMap &profiles)
-{
- d->storedProfiles = profiles;
-}
-
-void ProjectTreeBuilder::setStoredModuleProviderInfo(
- const StoredModuleProviderInfo &moduleProviderInfo)
-{
- d->state.dependenciesResolver().setStoredModuleProviderInfo(moduleProviderInfo);
-}
-
-ProjectTreeBuilder::Result ProjectTreeBuilder::load()
-{
- auto mainTimer = std::make_unique<TimedActivityLogger>(
- d->state.logger(), Tr::tr("ProjectTreeBuilder"),
- d->state.parameters().logElapsedTime());
- qCDebug(lcModuleLoader) << "load" << d->state.parameters().projectFilePath();
-
- d->checkOverriddenValues();
-
- Result result;
- TopLevelProjectContext &project = d->state.topLevelProject();
- project.profileConfigs = d->storedProfiles;
- result.root = d->loadTopLevelProjectItem();
- d->handleTopLevelProject(result.root);
-
- result.qbsFiles = d->state.itemReader().filesRead()
- - d->state.dependenciesResolver() .tempQbsFiles();
- result.productInfos = project.productInfos;
- result.profileConfigs = project.profileConfigs;
- const QVariantMap &profiles = d->state.localProfiles().profiles();
- for (auto it = profiles.begin(); it != profiles.end(); ++it)
- result.profileConfigs.remove(it.key());
- result.projectProbes = project.probes;
- result.storedModuleProviderInfo = d->state.dependenciesResolver().storedModuleProviderInfo();
-
- mainTimer.reset();
- d->printProfilingInfo();
-
- return result;
-}
-
-Item *ProjectTreeBuilder::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:
- return state.itemReader().wrapInProjectIfNecessary(root);
- case ItemType::Project:
- return root;
- 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 ProjectTreeBuilder::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()});
- for (const auto &prefix : prefixes) {
- if (key.startsWith(prefix + QLatin1Char('.')))
- return true;
- }
- return false;
- };
- 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 ProjectTreeBuilder::Private::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()) {
- state.topLevelProject().projectNamesUsedInOverrides.insert(projectName);
- return;
- }
- const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString);
- if (!productName.isEmpty()) {
- state.topLevelProject().productNamesUsedInOverrides.insert(productName.left(
- productName.indexOf(StringConstants::dot())));
- return;
- }
-}
-
-void ProjectTreeBuilder::Private::handleTopLevelProject(Item *projectItem)
-{
- state.topLevelProject().buildDirectory = TopLevelProject::deriveBuildDirectory(
- state.parameters().buildRoot(),
- TopLevelProject::deriveId(state.parameters().finalBuildConfigurationTree()));
- projectItem->setProperty(StringConstants::sourceDirectoryProperty(),
- VariantValue::create(QFileInfo(projectItem->file()->filePath())
- .absolutePath()));
- projectItem->setProperty(StringConstants::buildDirectoryProperty(),
- VariantValue::create(state.topLevelProject().buildDirectory));
- projectItem->setProperty(StringConstants::profileProperty(),
- VariantValue::create(state.parameters().topLevelProfile()));
- productsCollector.run(projectItem);
- productsHandler.run();
-
- state.itemReader().clearExtraSearchPathsStack(); // TODO: Unneeded?
- AccumulatingTimer timer(state.parameters().logElapsedTime()
- ? &elapsedTimePropertyChecking : nullptr);
- checkPropertyDeclarations(projectItem, state.topLevelProject().disabledItems,
- state.parameters(), state.logger());
-}
-
-void ProjectTreeBuilder::Private::printProfilingInfo()
-{
- if (!state.parameters().logElapsedTime())
- return;
- state.logger().qbsLog(LoggerInfo, true)
- << " "
- << Tr::tr("Project file loading and parsing took %1.")
- .arg(elapsedTimeString(state.itemReader().elapsedTime()));
- productsCollector.printProfilingInfo(2);
- productsHandler.printProfilingInfo(2);
- state.dependenciesResolver().printProfilingInfo(4);
- state.moduleInstantiator().printProfilingInfo(6);
- state.propertyMerger().printProfilingInfo(6);
- state.probesResolver().printProfilingInfo(4);
- state.logger().qbsLog(LoggerInfo, true)
- << " "
- << Tr::tr("Property checking took %1.")
- .arg(elapsedTimeString(elapsedTimePropertyChecking));
-}
-
-} // namespace qbs::Internal
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/logger.cpp b/src/lib/corelib/logging/logger.cpp
index d4f51cd30..65d0cc27f 100644
--- a/src/lib/corelib/logging/logger.cpp
+++ b/src/lib/corelib/logging/logger.cpp
@@ -231,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/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.h b/src/lib/corelib/parser/qmljsengine_p.h
index 2a616126d..9c603ee5c 100644
--- a/src/lib/corelib/parser/qmljsengine_p.h
+++ b/src/lib/corelib/parser/qmljsengine_p.h
@@ -93,7 +93,7 @@ public:
QString message;
};
-class QBS_AUTOTEST_EXPORT Engine
+class QML_PARSER_EXPORT Engine
{
Lexer *_lexer{nullptr};
Directives *_directives{nullptr};
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/qmljslexer.cpp b/src/lib/corelib/parser/qmljslexer.cpp
index 684be9317..e148652ad 100644
--- a/src/lib/corelib/parser/qmljslexer.cpp
+++ b/src/lib/corelib/parser/qmljslexer.cpp
@@ -1071,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();
@@ -1083,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
@@ -1126,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 aef68e0c5..c9801c0f5 100644
--- a/src/lib/corelib/parser/qmljslexer_p.h
+++ b/src/lib/corelib/parser/qmljslexer_p.h
@@ -86,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_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/clangclinfo.cpp b/src/lib/corelib/tools/clangclinfo.cpp
index 68486b91f..fd907ebf1 100644
--- a/src/lib/corelib/tools/clangclinfo.cpp
+++ b/src/lib/corelib/tools/clangclinfo.cpp
@@ -47,7 +47,7 @@ static std::vector<MSVCInstallInfo> compatibleMsvcs(Logger &logger)
return true;
bool ok = false;
const int major = versions.at(0).toInt(&ok);
- return !(ok && major >= 15); // support MSVC2017 and above
+ return !ok || major < 15; // support MSVC2017 and above
};
Internal::removeIf(msvcs, filter);
for (const auto &msvc: msvcs) {
@@ -121,7 +121,7 @@ std::vector<ClangClInfo> ClangClInfo::installedCompilers(
if (registry.contains(key)) {
const auto compilerPath = QDir::fromNativeSeparators(registry.value(key).toString())
+ QStringLiteral("/bin/") + compilerName;
- if (QFileInfo::exists(compilerPath))
+ if (QFileInfo::exists(compilerPath) && !contains(compilerPaths, compilerPath))
compilerPaths.push_back(compilerPath);
}
diff --git a/src/lib/corelib/tools/codelocation.cpp b/src/lib/corelib/tools/codelocation.cpp
index ea77fee9d..f04041e14 100644
--- a/src/lib/corelib/tools/codelocation.cpp
+++ b/src/lib/corelib/tools/codelocation.cpp
@@ -182,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 33b832df0..afcd2e075 100644
--- a/src/lib/corelib/tools/codelocation.h
+++ b/src/lib/corelib/tools/codelocation.h
@@ -86,11 +86,79 @@ 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);
+class QBS_EXPORT CodePosition
+{
+public:
+ CodePosition(int line, int column) : m_line(line), m_column(column) {}
+
+ 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
#endif // QBS_SOURCELOCATION_H
diff --git a/src/lib/corelib/tools/error.cpp b/src/lib/corelib/tools/error.cpp
index 963089fe8..f1b90b71e 100644
--- a/src/lib/corelib/tools/error.cpp
+++ b/src/lib/corelib/tools/error.cpp
@@ -317,6 +317,13 @@ bool ErrorInfo::hasLocation() const
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)
{
pool.load(*d);
diff --git a/src/lib/corelib/tools/error.h b/src/lib/corelib/tools/error.h
index 4c6370b1e..ba600a558 100644
--- a/src/lib/corelib/tools/error.h
+++ b/src/lib/corelib/tools/error.h
@@ -113,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;
diff --git a/src/lib/corelib/tools/executablefinder.cpp b/src/lib/corelib/tools/executablefinder.cpp
index 0bdc861fa..b5d9c0151 100644
--- a/src/lib/corelib/tools/executablefinder.cpp
+++ b/src/lib/corelib/tools/executablefinder.cpp
@@ -120,7 +120,7 @@ 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(), QBS_SKIP_EMPTY_PARTS);
+ .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
if (HostOsInfo::isWindowsHost())
pathEnv.prepend(StringConstants::dot());
for (QString directory : std::as_const(pathEnv)) {
diff --git a/src/lib/corelib/tools/launchersocket.cpp b/src/lib/corelib/tools/launchersocket.cpp
index 4b72d7580..7d4788ee3 100644
--- a/src/lib/corelib/tools/launchersocket.cpp
+++ b/src/lib/corelib/tools/launchersocket.cpp
@@ -82,12 +82,7 @@ void LauncherSocket::setSocket(QLocalSocket *socket)
QBS_ASSERT(!m_socket, return);
m_socket.store(socket);
m_packetParser.setDevice(m_socket);
- connect(m_socket,
-#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
- static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
-#else
- &QLocalSocket::errorOccurred,
-#endif
+ connect(m_socket, &QLocalSocket::errorOccurred,
this, &LauncherSocket::handleSocketError);
connect(m_socket, &QLocalSocket::readyRead,
this, &LauncherSocket::handleSocketDataAvailable);
diff --git a/src/lib/corelib/tools/msvcinfo.cpp b/src/lib/corelib/tools/msvcinfo.cpp
index 973284b2b..77b83023a 100644
--- a/src/lib/corelib/tools/msvcinfo.cpp
+++ b/src/lib/corelib/tools/msvcinfo.cpp
@@ -176,6 +176,7 @@ static QVariantMap getMsvcDefines(const QString &compilerFilePath,
<< qEnvironmentVariable("COMSPEC")
<< QStringLiteral("/c")
<< languageSwitch
+ << QStringLiteral("/Zs")
<< QStringLiteral("NUL"),
compilerEnv, true, commands)).split(QLatin1Char('\n'));
diff --git a/src/lib/corelib/tools/mutexdata.h b/src/lib/corelib/tools/mutexdata.h
new file mode 100644
index 000000000..160f6dd26
--- /dev/null
+++ b/src/lib/corelib/tools/mutexdata.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <shared_mutex>
+
+namespace qbs {
+namespace Internal {
+
+// 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:
+ 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;
+
+ T &get() const noexcept { return *m_ptr; }
+ T &data() const noexcept { return *m_ptr; }
+ operator T &() const noexcept { return *m_ptr; }
+
+ private:
+ T *m_ptr;
+ LockType<MutexType> m_lock;
+ };
+
+ using UniqueMutableGuard = ReferenceGuard<DataType, std::unique_lock>;
+ using UniqueConstGuard = ReferenceGuard<const DataType, std::unique_lock>;
+ using SharedGuard = ReferenceGuard<const DataType, std::shared_lock>;
+
+ 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}; }
+
+ 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 qbs
+} // namespace Internal
diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp
index cf5903349..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-132";
+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,6 +140,11 @@ void PersistentPool::finalizeWriteStream()
void PersistentPool::storeVariant(const QVariant &variant)
{
+ 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) {
@@ -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 b7aa543a4..86365c993 100644
--- a/src/lib/corelib/tools/persistence.h
+++ b/src/lib/corelib/tools/persistence.h
@@ -145,8 +145,9 @@ private:
template<typename T> QHash<T, PersistentObjectId> &idMap();
template<typename T> PersistentObjectId &lastStoredId();
- 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;
@@ -271,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;
@@ -287,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;
diff --git a/src/lib/corelib/tools/profiling.cpp b/src/lib/corelib/tools/profiling.cpp
index db64a73c6..b93d3fa17 100644
--- a/src/lib/corelib/tools/profiling.cpp
+++ b/src/lib/corelib/tools/profiling.cpp
@@ -72,7 +72,7 @@ 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);
d.reset();
@@ -98,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 c6fc9ede7..3220c2a82 100644
--- a/src/lib/corelib/tools/profiling.h
+++ b/src/lib/corelib/tools/profiling.h
@@ -52,7 +52,7 @@ namespace qbs {
namespace Internal {
class Logger;
-QString elapsedTimeString(qint64 elapsedTimeInMs);
+QString elapsedTimeString(qint64 elapsedTimeInNs);
class TimedActivityLogger
{
diff --git a/src/lib/corelib/tools/progressobserver.h b/src/lib/corelib/tools/progressobserver.h
index 9acb4b30c..73a61de37 100644
--- a/src/lib/corelib/tools/progressobserver.h
+++ b/src/lib/corelib/tools/progressobserver.h
@@ -41,6 +41,8 @@
#include <QtCore/qglobal.h>
+#include <vector>
+
QT_BEGIN_NAMESPACE
class QString;
QT_END_NAMESPACE
@@ -66,13 +68,13 @@ public:
// Call this to ensure that the progress bar always goes to 100%.
void setFinished();
- void setScriptEngine(ScriptEngine *engine) { m_scriptEngine = engine; }
+ void addScriptEngine(ScriptEngine *engine) { m_scriptEngines.push_back(engine); }
protected:
- ScriptEngine *scriptEngine() const { return m_scriptEngine; }
+ const std::vector<ScriptEngine *> &scriptEngines() const { return m_scriptEngines; }
private:
- ScriptEngine *m_scriptEngine = nullptr;
+ std::vector<ScriptEngine *> m_scriptEngines;
};
} // namespace Internal
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/qttools.h b/src/lib/corelib/tools/qttools.h
index bc1210d53..029948be4 100644
--- a/src/lib/corelib/tools/qttools.h
+++ b/src/lib/corelib/tools/qttools.h
@@ -55,19 +55,7 @@ QT_BEGIN_NAMESPACE
class QProcessEnvironment;
QT_END_NAMESPACE
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
-#define QBS_SKIP_EMPTY_PARTS QString::SkipEmptyParts
-#else
-#define QBS_SKIP_EMPTY_PARTS Qt::SkipEmptyParts
-#endif
-
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
@@ -161,12 +149,6 @@ inline qbs::QHashValueType qHash(const QVariantHash &v)
return std::hash<QVariantHash>()(v) % std::numeric_limits<uint>::max();
}
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
-namespace Qt {
-inline QTextStream &endl(QTextStream &stream) { return stream << QT_PREPEND_NAMESPACE(endl); }
-} // namespace Qt
-#endif
-
QT_END_NAMESPACE
namespace qbs {
@@ -174,32 +156,20 @@ 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());
-#endif
}
template<class T>
QList<T> toList(const QSet<T> &set)
{
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
- return set.toList();
-#else
return QList<T>(set.begin(), set.end());
-#endif
}
template<typename K, typename V>
QHash<K, V> &unite(QHash<K, V> &h, const QHash<K, V> &other)
{
-#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
- return h.unite(other);
-#else
h.insert(other);
return h;
-#endif
}
inline void setupDefaultCodec(QTextStream &stream)
@@ -229,6 +199,51 @@ inline bool qVariantConvert(QVariant &variant, int typeId)
#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 83005c788..e89e4a0ad 100644
--- a/src/lib/corelib/tools/scripttools.cpp
+++ b/src/lib/corelib/tools/scripttools.cpp
@@ -123,7 +123,7 @@ ErrorInfo JsException::toErrorInfo() const
ErrorInfo e(msg, stackTrace());
if (e.hasLocation() || !m_fallbackLocation.isValid())
return e;
- return ErrorInfo(msg, m_fallbackLocation);
+ return {msg, m_fallbackLocation};
}
void defineJsProperty(JSContext *ctx, JSValueConst obj, const QString &prop, JSValue val)
@@ -231,44 +231,19 @@ QStringList getJsStringList(JSContext *ctx, JSValue val)
return l;
}
-JSValue makeJsVariant(JSContext *ctx, const QVariant &v)
+JSValue makeJsVariant(JSContext *ctx, const QVariant &v, quintptr id)
{
- switch (static_cast<QMetaType::Type>(v.userType())) {
- case QMetaType::QByteArray:
- return makeJsArrayBuffer(ctx, v.toByteArray());
- case QMetaType::QString:
- return makeJsString(ctx, v.toString());
- case QMetaType::QStringList:
- return makeJsStringList(ctx, v.toStringList());
- case QMetaType::QVariantList:
- return makeJsVariantList(ctx, v.toList());
- case QMetaType::Int:
- case QMetaType::UInt:
- return JS_NewInt32(ctx, v.toInt());
- case QMetaType::Long:
- case QMetaType::ULong:
- case QMetaType::LongLong:
- case QMetaType::ULongLong:
- return JS_NewInt64(ctx, v.toInt());
- case QMetaType::Bool:
- return JS_NewBool(ctx, v.toBool());
- case QMetaType::QDateTime:
- return JS_NewDate(ctx, v.toDateTime().toString(Qt::ISODateWithMs).toUtf8().constData());
- case QMetaType::QVariantMap:
- return makeJsVariantMap(ctx, v.toMap());
- default:
- return JS_UNDEFINED;
- }
+ return ScriptEngine::engineForContext(ctx)->asJsValue(v, id);
}
-JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l)
+JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l, quintptr id)
{
- return ScriptEngine::engineForContext(ctx)->asJsValue(l);
+ return ScriptEngine::engineForContext(ctx)->asJsValue(l, id);
}
-JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m)
+JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m, quintptr id)
{
- return ScriptEngine::engineForContext(ctx)->asJsValue(m);
+ return ScriptEngine::engineForContext(ctx)->asJsValue(m, id);
}
static QVariant getJsVariantImpl(JSContext *ctx, JSValue val, QList<JSValue> path)
@@ -318,7 +293,7 @@ static QVariant getJsVariantImpl(JSContext *ctx, JSValue val, QList<JSValue> pat
const auto tag = JS_VALUE_GET_TAG(val);
if (tag == JS_TAG_INT)
return JS_VALUE_GET_INT(val);
- else if (tag == JS_TAG_FLOAT64)
+ else if (JS_TAG_IS_FLOAT64(tag))
return JS_VALUE_GET_FLOAT64(val);
return {};
}
diff --git a/src/lib/corelib/tools/scripttools.h b/src/lib/corelib/tools/scripttools.h
index ea7993485..ac7ed9928 100644
--- a/src/lib/corelib/tools/scripttools.h
+++ b/src/lib/corelib/tools/scripttools.h
@@ -75,9 +75,9 @@ 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);
-JSValue makeJsVariantList(JSContext *ctx, const QVariantList &l);
-JSValue makeJsVariantMap(JSContext *ctx, const QVariantMap &m);
+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 &)>;
@@ -201,9 +201,11 @@ QVariant QBS_AUTOTEST_EXPORT getConfigProperty(const QVariantMap &cfg, const QSt
} // 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/settings.cpp b/src/lib/corelib/tools/settings.cpp
index 8b22c45e5..4a3d9d727 100644
--- a/src/lib/corelib/tools/settings.cpp
+++ b/src/lib/corelib/tools/settings.cpp
@@ -135,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;
}
diff --git a/src/lib/corelib/tools/settings.h b/src/lib/corelib/tools/settings.h
index 4ea148af0..7c9c52c9e 100644
--- a/src/lib/corelib/tools/settings.h
+++ b/src/lib/corelib/tools/settings.h
@@ -70,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);
diff --git a/src/lib/corelib/tools/settingsmodel.cpp b/src/lib/corelib/tools/settingsmodel.cpp
index 89d923496..4fa0e14da 100644
--- a/src/lib/corelib/tools/settingsmodel.cpp
+++ b/src/lib/corelib/tools/settingsmodel.cpp
@@ -281,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;
@@ -329,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('.'), QBS_SKIP_EMPTY_PARTS);
+ const QStringList nameAsList = it.key().split(QLatin1Char('.'), Qt::SkipEmptyParts);
addNode(&rootNode, nameAsList.front(), nameAsList.mid(1), it.value());
}
dirty = false;
diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp
index bc07acfef..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,6 +47,7 @@
#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>
@@ -90,12 +93,12 @@ 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;
@@ -154,12 +157,14 @@ 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");
@@ -373,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
@@ -490,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;
}
@@ -595,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
diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h
index 67bb5298a..5cd9700a9 100644
--- a/src/lib/corelib/tools/setupprojectparameters.h
+++ b/src/lib/corelib/tools/setupprojectparameters.h
@@ -99,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;
@@ -129,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;
diff --git a/src/lib/corelib/tools/shellutils.cpp b/src/lib/corelib/tools/shellutils.cpp
index d032aecac..5fff254f6 100644
--- a/src/lib/corelib/tools/shellutils.cpp
+++ b/src/lib/corelib/tools/shellutils.cpp
@@ -57,7 +57,7 @@ QString shellInterpreter(const QString &filePath) {
const QString shebang = ts.readLine();
if (shebang.startsWith(QLatin1String("#!"))) {
return (shebang.mid(2).split(QRegularExpression(QStringLiteral("\\s")),
- QBS_SKIP_EMPTY_PARTS) << QString()).front();
+ Qt::SkipEmptyParts) << QString()).front();
}
}
diff --git a/src/lib/corelib/tools/stringconstants.h b/src/lib/corelib/tools/stringconstants.h
index 45903032c..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")
diff --git a/src/lib/msbuild/io/visualstudiosolutionwriter.cpp b/src/lib/msbuild/io/visualstudiosolutionwriter.cpp
index 625489ac6..874fbc71f 100644
--- a/src/lib/msbuild/io/visualstudiosolutionwriter.cpp
+++ b/src/lib/msbuild/io/visualstudiosolutionwriter.cpp
@@ -108,20 +108,6 @@ bool VisualStudioSolutionWriter::write(const VisualStudioSolution *solution)
<< project->guid().toString().toStdString()
<< u8"\"\n";
- const auto dependencies = solution->dependencies(project);
- if (!dependencies.empty()) {
- out << u8"\tProjectSection(ProjectDependencies) = postProject\n";
-
- for (const auto &dependency : dependencies)
- out << u8"\t\t"
- << dependency->guid().toString().toStdString()
- << u8" = "
- << dependency->guid().toString().toStdString()
- << u8"\n";
-
- out << u8"\tEndProjectSection\n";
- }
-
out << u8"EndProject\n";
}
diff --git a/src/lib/pkgconfig/pcpackage.cpp b/src/lib/pkgconfig/pcpackage.cpp
index 984936bd1..9aaefcfde 100644
--- a/src/lib/pkgconfig/pcpackage.cpp
+++ b/src/lib/pkgconfig/pcpackage.cpp
@@ -43,6 +43,8 @@
namespace qbs {
+using Internal::startsWith;
+
using ComparisonType = PcPackage::RequiredVersion::ComparisonType;
std::string_view PcPackage::Flag::typeToString(Type t)
@@ -123,6 +125,26 @@ std::optional<ComparisonType> PcPackage::RequiredVersion::comparisonFromString(s
return std::nullopt;
}
+// see https://github.com/pkgconf/pkgconf/blob/pkgconf-2.1.0/libpkgconf/tuple.c#L194
+bool PcPackage::shouldRewriteSysroot(std::string_view sysroot, std::string_view value)
+{
+ if (sysroot.empty() || value.empty())
+ return false;
+
+ if (value.front() != '/')
+ return false;
+
+ if (sysroot == "/")
+ return false;
+
+ if (startsWith(value, sysroot))
+ return false;
+
+ return true;
+}
+
+// TODO: pkg-config only prepends sysroot to flags; while pkgconf does this as a part of the
+// variable substitution and thus this affects all variables, not only flags
PcPackage PcPackage::prependSysroot(std::string_view sysroot) &&
{
PcPackage package(std::move(*this));
@@ -132,6 +154,8 @@ PcPackage PcPackage::prependSysroot(std::string_view sysroot) &&
if (sysroot.empty())
return flags;
for (auto &flag : flags) {
+ if (!shouldRewriteSysroot(sysroot, flag.value))
+ continue;
if (flag.type == Flag::Type::IncludePath
|| flag.type == Flag::Type::SystemIncludePath
|| flag.type == Flag::Type::DirAfterIncludePath
@@ -169,4 +193,51 @@ PcPackage PcPackage::removeSystemLibraryPaths(
return package;
}
+namespace Internal {
+
+std::string_view fileName(std::string_view filePath)
+{
+ const auto pos = filePath.rfind('/');
+ if (pos == std::string_view::npos) {
+#if defined(WIN32)
+ if (filePath.size() >= 2 && filePath[1] == ':')
+ return filePath.substr(2);
+#endif
+ return filePath;
+ }
+ return filePath.substr(pos + 1);
+}
+
+std::string_view completeBaseName(std::string_view filePath)
+{
+ filePath = fileName(filePath);
+ const auto pos = filePath.rfind('.');
+ return pos == std::string_view::npos
+ ? filePath
+ : filePath.substr(0, pos);
+}
+
+std::string_view parentPath(std::string_view path)
+{
+ if (path.empty())
+ return {};
+ auto pos = path.rfind('/');
+ if (pos == std::string_view::npos) {
+#if defined(WIN32)
+ if (path.size() >= 2 && path[1] == ':')
+ return path.substr(0, 2);
+#endif
+ return ".";
+ }
+ if (pos == 0)
+ return "/";
+#if defined(WIN32)
+ if (pos == 2 && path[1] == ':')
+ return path.substr(0, pos + 1);
+#endif
+ return path.substr(0, pos);
+};
+
+} // namespace Internal
+
} // namespace qbs
diff --git a/src/lib/pkgconfig/pcpackage.h b/src/lib/pkgconfig/pcpackage.h
index 9d86816aa..794e2fc40 100644
--- a/src/lib/pkgconfig/pcpackage.h
+++ b/src/lib/pkgconfig/pcpackage.h
@@ -112,12 +112,14 @@ public:
std::vector<RequiredVersion> requiresPublic;
std::vector<RequiredVersion> requiresPrivate;
std::vector<RequiredVersion> conflicts;
+ std::optional<std::string> oldPrefix;
using VariablesMap = std::map<std::string, std::string, std::less<>>;
VariablesMap variables;
bool uninstalled{false};
+ static bool shouldRewriteSysroot(std::string_view sysroot, std::string_view value);
PcPackage prependSysroot(std::string_view sysroot) &&;
PcPackage removeSystemLibraryPaths(const std::unordered_set<std::string> &libraryPaths) &&;
};
@@ -181,6 +183,26 @@ inline bool operator!=(const PcPackage::Flag &lhs, const PcPackage::Flag &rhs)
return !(lhs == rhs);
}
+namespace Internal {
+
+// fast versions (no allocations) of the QFileInfo methods
+std::string_view fileName(std::string_view filePath);
+std::string_view completeBaseName(std::string_view filePath);
+std::string_view parentPath(std::string_view path);
+
+inline bool startsWith(std::string_view haystack, std::string_view needle)
+{
+ return haystack.size() >= needle.size() && haystack.compare(0, needle.size(), needle) == 0;
+}
+
+inline bool endsWith(std::string_view haystack, std::string_view needle)
+{
+ return haystack.size() >= needle.size()
+ && haystack.compare(haystack.size() - needle.size(), needle.size(), needle) == 0;
+}
+
+} // Internal
+
} // namespace qbs
namespace std {
diff --git a/src/lib/pkgconfig/pcparser.cpp b/src/lib/pkgconfig/pcparser.cpp
index 388363af6..eb07aa5ef 100644
--- a/src/lib/pkgconfig/pcparser.cpp
+++ b/src/lib/pkgconfig/pcparser.cpp
@@ -62,6 +62,11 @@ namespace std {
namespace qbs {
+using Internal::completeBaseName;
+using Internal::parentPath;
+using Internal::startsWith;
+using Internal::endsWith;
+
namespace {
// workaround for a missing ctor before c++20
@@ -195,17 +200,6 @@ std::optional<std::vector<std::string>> splitCommand(std::string_view s)
return result;
}
-bool startsWith(std::string_view haystack, std::string_view needle)
-{
- return haystack.size() >= needle.size() && haystack.compare(0, needle.size(), needle) == 0;
-}
-
-bool endsWith(std::string_view haystack, std::string_view needle)
-{
- return haystack.size() >= needle.size()
- && haystack.compare(haystack.size() - needle.size(), needle.size(), needle) == 0;
-}
-
[[noreturn]] void raizeUnknownComparisonException(const PcPackage &pkg, std::string_view verName, std::string_view comp)
{
std::string message;
@@ -397,17 +391,6 @@ PcPackage::RequiredVersion::ComparisonType comparisonFromString(
raizeUnknownComparisonException(pkg, verName, comp);
}
-std::string baseName(const std::string_view &filePath)
-{
- auto pos = filePath.rfind('/');
- const auto fileName =
- pos == std::string_view::npos ? std::string_view() : filePath.substr(pos + 1);
- pos = fileName.rfind('.');
- return std::string(pos == std::string_view::npos
- ? std::string_view()
- : fileName.substr(0, pos));
-}
-
} // namespace
PcParser::PcParser(const PkgConfig &pkgConfig)
@@ -429,7 +412,7 @@ try
if (!file.is_open())
throw PcException(std::string("Can't open file ") + path);
- package.baseFileName = baseName(path);
+ package.baseFileName = std::string{completeBaseName(path)};
#if HAS_STD_FILESYSTEM
const auto fsPath = std::filesystem::path(path);
package.filePath = fsPath.generic_string();
@@ -445,7 +428,7 @@ try
parseLine(package, line);
return package;
} catch(const PcException &ex) {
- return PcBrokenPackage{path, baseName(path), ex.what()};
+ return PcBrokenPackage{path, std::string{completeBaseName(path)}, ex.what()};
}
std::string PcParser::trimAndSubstitute(const PcPackage &pkg, std::string_view str) const
@@ -488,6 +471,35 @@ std::string PcParser::trimAndSubstitute(const PcPackage &pkg, std::string_view s
return result;
}
+std::string PcParser::evaluateVariable(
+ PcPackage &pkg, std::string_view tag, std::string_view str) const
+{
+ static constexpr std::string_view prefixVariable = "prefix";
+ if (m_pkgConfig.options().definePrefix) {
+ if (tag == prefixVariable) {
+ std::string_view prefix = pkg.filePath;
+ prefix = parentPath(prefix);
+ if (completeBaseName(prefix) == "pkgconfig") {
+ prefix = parentPath(prefix);
+ prefix = parentPath(prefix);
+ }
+ pkg.oldPrefix = std::string(str);
+ if (!prefix.empty())
+ str = prefix;
+ return std::string(str);
+ } else if (pkg.oldPrefix
+ && str.size() > pkg.oldPrefix->size()
+ && str.substr(0, pkg.oldPrefix->size()) == *pkg.oldPrefix
+ && str[pkg.oldPrefix->size()] == '/') {
+ auto result = pkg.variables["prefix"];
+ result += str.substr(pkg.oldPrefix->size());
+ return trimAndSubstitute(pkg, result);
+ }
+ }
+
+ return trimAndSubstitute(pkg, str);
+}
+
void PcParser::parseStringField(
PcPackage &pkg,
std::string &field,
@@ -512,9 +524,11 @@ void PcParser::parseLibs(
raiseDuplicateFieldException(fieldName, pkg.filePath);
const auto trimmed = trimAndSubstitute(pkg, str);
+ if (trimmed.empty())
+ return;
const auto argv = splitCommand(trimmed);
- if (!trimmed.empty() && !argv)
+ if (!argv)
throw PcException("Couldn't parse Libs field into an argument vector");
libs = doParseLibs(*argv);
@@ -581,9 +595,11 @@ void PcParser::parseCFlags(PcPackage &pkg, std::string_view str)
raiseDuplicateFieldException("Cflags", pkg.filePath);
const auto command = trimAndSubstitute(pkg, str);
+ if (command.empty())
+ return;
const auto argv = splitCommand(command);
- if (!command.empty() && !argv)
+ if (!argv)
throw PcException("Couldn't parse Cflags field into an argument vector");
std::vector<PcPackage::Flag> cflags;
@@ -710,10 +726,8 @@ void PcParser::parseLine(PcPackage &pkg, std::string_view str)
size_t pos = 0;
for (; pos < s.size(); ++pos) {
auto p = s.data() + pos;
- if (!((*p >= 'A' && *p <= 'Z') ||
- (*p >= 'a' && *p <= 'z') ||
- (*p >= '0' && *p <= '9') ||
- *p == '_' || *p == '.')) {
+ if ((*p < 'A' || *p > 'Z') && (*p < 'a' || *p > 'z') && (*p < '0' || *p > '9')
+ && *p != '_' && *p != '.') {
break;
}
}
@@ -765,14 +779,7 @@ void PcParser::parseLine(PcPackage &pkg, std::string_view str)
str.remove_prefix(1); // cut '='
str = trimmed(str);
- // TODO: support guesstimating of the prefix variable (pkg-config's --define-prefix option)
- // from doc: "try to override the value of prefix for each .pc file found with a
- // guesstimated value based on the location of the .pc file"
- // https://gitlab.freedesktop.org/pkg-config/pkg-config/-/blob/pkg-config-0.29.2/parse.c#L998
- // This option is disabled by default, and Qbs doesn't allow to override it yet, so we can
- // ignore this feature for now
-
- const auto value = trimAndSubstitute(pkg, str);
+ const auto value = evaluateVariable(pkg, tag, str);
if (!pkg.variables.insert({std::string(tag), value}).second)
raizeDuplicateVariableException(pkg, tag);
}
diff --git a/src/lib/pkgconfig/pcparser.h b/src/lib/pkgconfig/pcparser.h
index ffdf86aaa..a8a5e5cd4 100644
--- a/src/lib/pkgconfig/pcparser.h
+++ b/src/lib/pkgconfig/pcparser.h
@@ -55,6 +55,7 @@ public:
private:
std::string trimAndSubstitute(const PcPackage &pkg, std::string_view str) const;
+ std::string evaluateVariable(PcPackage &pkg, std::string_view tag, std::string_view str) const;
void parseStringField(
PcPackage &pkg,
std::string &field,
diff --git a/src/lib/pkgconfig/pkgconfig.cpp b/src/lib/pkgconfig/pkgconfig.cpp
index b2c3d4d71..6b3bde46c 100644
--- a/src/lib/pkgconfig/pkgconfig.cpp
+++ b/src/lib/pkgconfig/pkgconfig.cpp
@@ -106,50 +106,6 @@ constexpr inline char listSeparator() noexcept
#endif
}
-// based on https://stackoverflow.com/a/33135699/295518
-int compareVersions(std::string_view v1, std::string_view v2)
-{
- for (size_t i = 0, j = 0; i < v1.length() || j < v2.length(); ) {
- size_t acc1 = 0;
- size_t acc2 = 0;
-
- while (i < v1.length() && v1[i] != '.') {
- acc1 = acc1 * 10 + (v1[i] - '0');
- i++;
- }
- while (j < v2.length() && v2[j] != '.') {
- acc2 = acc2 * 10 + (v2[j] - '0');
- j++;
- }
-
- if (acc1 < acc2)
- return -1;
- if (acc1 > acc2)
- return +1;
-
- ++i;
- ++j;
- }
- return 0;
-}
-
-using ComparisonType = PcPackage::RequiredVersion::ComparisonType;
-
-bool versionTest(ComparisonType comparison, std::string_view a, std::string_view b)
-{
- switch (comparison) {
- case ComparisonType::LessThan: return compareVersions(a, b) < 0;
- case ComparisonType::GreaterThan: return compareVersions(a, b) > 0;
- case ComparisonType::LessThanEqual: return compareVersions(a, b) <= 0;
- case ComparisonType::GreaterThanEqual: return compareVersions(a, b) >= 0;
- case ComparisonType::Equal: return compareVersions(a, b) == 0;
- case ComparisonType::NotEqual: return compareVersions(a, b) != 0;
- case ComparisonType::AlwaysMatch: return true;
- }
-
- return false;
-}
-
[[noreturn]] void raizeUnknownPackageException(std::string_view package)
{
std::string message;
@@ -334,116 +290,6 @@ PcBrokenPackage makeVersionMismatchDependency(
package.filePath, package.baseFileName, std::move(message)};
}
-PkgConfig::Packages PkgConfig::mergeDependencies(const PkgConfig::Packages &packages) const
-{
- std::unordered_map<std::string_view, const PcPackageVariant *> packageHash;
-
- struct MergedHashEntry
- {
- PcPackageVariant package; // merged package or broken package
- std::vector<const PcPackage *> deps; // unmerged transitive deps, including Package itself
- };
- std::unordered_map<std::string, MergedHashEntry> mergedHash;
-
- for (const auto &package: packages)
- packageHash[package.getBaseFileName()] = &package;
-
- auto func = [&](const PcPackageVariant &package, auto &f) -> const MergedHashEntry &
- {
- const auto it = mergedHash.find(package.getBaseFileName());
- if (it != mergedHash.end())
- return it->second;
-
- auto &entry = mergedHash[package.getBaseFileName()];
-
- auto visitor = [&](auto &&package) -> PcPackageVariant {
-
- using T = std::decay_t<decltype(package)>;
- if constexpr (std::is_same_v<T, PcPackage>) { // NOLINT
-
- using Flags = std::vector<PcPackage::Flag>;
-
- // returns true if multiple copies of the flag can present in the same package
- // we can't properly merge flags that have multiple parameters except for
- // -framework which we handle correctly.
- auto canHaveDuplicates = [](const PcPackage::Flag::Type &type) {
- return type == PcPackage::Flag::Type::LinkerFlag
- || type == PcPackage::Flag::Type::CompilerFlag;
- };
-
- std::unordered_set<PcPackage::Flag> visitedFlags;
- // appends only those flags to the target that were not seen before (except for
- // ones that can have duplicates)
- auto mergeFlags = [&](Flags &target, const Flags &source)
- {
- for (const auto &flag: source) {
- if (canHaveDuplicates(flag.type) || visitedFlags.insert(flag).second)
- target.push_back(flag);
- }
- };
-
- std::unordered_set<const PcPackage *> visitedDeps;
-
- PcPackage result;
- // copy only meta info for now
- result.filePath = package.filePath;
- result.baseFileName = package.baseFileName;
- result.name = package.name;
- result.version = package.version;
- result.description = package.description;
- result.url = package.url;
- result.variables = package.variables;
-
- auto allDependencies = package.requiresPublic;
- if (m_options.staticMode)
- allDependencies << package.requiresPrivate;
-
- for (const auto &dependency: allDependencies) {
- const auto it = packageHash.find(dependency.name);
- if (it == packageHash.end())
- return makeMissingDependency(result, dependency);
-
- const auto childEntry = f(*it->second, f);
- if (childEntry.package.isBroken())
- return makeBrokenDependency(result, dependency);
-
- const auto &mergedPackage = childEntry.package.asPackage();
- const bool versionOk = versionTest(
- dependency.comparison, mergedPackage.version, dependency.version);
- if (!versionOk)
- return makeVersionMismatchDependency(result, mergedPackage, dependency);
-
- for (const auto *dep: childEntry.deps) {
- if (visitedDeps.insert(dep).second)
- entry.deps.push_back(dep);
- }
- }
-
- entry.deps.push_back(&package);
-
- for (const auto *dep: entry.deps) {
- mergeFlags(result.libs, dep->libs);
- mergeFlags(result.cflags, dep->cflags);
- }
-
- return result;
- }
- return package;
- };
- entry.package = package.visit(visitor);
-
- return entry;
- };
-
- for (const auto &package: packages)
- func(package, func);
-
- Packages result;
- for (auto &[key, value]: mergedHash)
- result.push_back(std::move(value.package));
- return result;
-}
-
PkgConfig::Packages PkgConfig::findPackages() const
{
Packages result;
@@ -480,9 +326,6 @@ PkgConfig::Packages PkgConfig::findPackages() const
result.emplace_back(std::move(pkg));
}
- if (m_options.mergeDependencies)
- result = mergeDependencies(result);
-
const auto lessThanPackage = [](const PcPackageVariant &lhs, const PcPackageVariant &rhs)
{
return lhs.getBaseFileName() < rhs.getBaseFileName();
diff --git a/src/lib/pkgconfig/pkgconfig.h b/src/lib/pkgconfig/pkgconfig.h
index c1cc634e3..fd4cc3253 100644
--- a/src/lib/pkgconfig/pkgconfig.h
+++ b/src/lib/pkgconfig/pkgconfig.h
@@ -58,7 +58,7 @@ public:
std::vector<std::string> systemLibraryPaths; // PKG_CONFIG_SYSTEM_LIBRARY_PATH
bool disableUninstalled{true}; // PKG_CONFIG_DISABLE_UNINSTALLED
bool staticMode{false};
- bool mergeDependencies{true};
+ bool definePrefix{false};
VariablesMap globalVariables;
VariablesMap systemVariables;
};
@@ -77,7 +77,6 @@ public:
private:
Packages findPackages() const;
- Packages mergeDependencies(const Packages &packages) const;
private:
Options m_options;
diff --git a/src/libexec/qbs_processlauncher/launchersockethandler.cpp b/src/libexec/qbs_processlauncher/launchersockethandler.cpp
index 637362b88..037f061a5 100644
--- a/src/libexec/qbs_processlauncher/launchersockethandler.cpp
+++ b/src/libexec/qbs_processlauncher/launchersockethandler.cpp
@@ -121,12 +121,7 @@ void LauncherSocketHandler::start()
connect(m_socket, &QLocalSocket::disconnected,
this, &LauncherSocketHandler::handleSocketClosed);
connect(m_socket, &QLocalSocket::readyRead, this, &LauncherSocketHandler::handleSocketData);
- connect(m_socket,
-#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
- static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
-#else
- &QLocalSocket::errorOccurred,
-#endif
+ connect(m_socket, &QLocalSocket::errorOccurred,
this, &LauncherSocketHandler::handleSocketError);
m_socket->connectToServer(m_serverPath);
}
diff --git a/src/plugins/generator/visualstudio/msbuildfiltersproject.cpp b/src/plugins/generator/visualstudio/msbuildfiltersproject.cpp
index 039b1173e..127d6a438 100644
--- a/src/plugins/generator/visualstudio/msbuildfiltersproject.cpp
+++ b/src/plugins/generator/visualstudio/msbuildfiltersproject.cpp
@@ -45,110 +45,203 @@
namespace qbs {
-static QStringList sourceFileExtensions()
+namespace {
+
+const QStringList & sourceFileExtensions()
{
- return {QStringLiteral("c"), QStringLiteral("C"), QStringLiteral("cpp"),
- QStringLiteral("cxx"), QStringLiteral("c++"), QStringLiteral("cc"),
- QStringLiteral("cs"), QStringLiteral("def"), QStringLiteral("java"),
- QStringLiteral("m"), QStringLiteral("mm")};
+ static const QStringList EXTENSIONS{
+ QStringLiteral("c"),
+ QStringLiteral("C"),
+ QStringLiteral("cpp"),
+ QStringLiteral("cxx"),
+ QStringLiteral("c++"),
+ QStringLiteral("cc"),
+ QStringLiteral("cs"),
+ QStringLiteral("def"),
+ QStringLiteral("java"),
+ QStringLiteral("m"),
+ QStringLiteral("mm")};
+
+ return EXTENSIONS;
}
-static QStringList headerFileExtensions()
+const QStringList & headerFileExtensions()
{
- return {QStringLiteral("h"), QStringLiteral("H"), QStringLiteral("hpp"),
- QStringLiteral("hxx"), QStringLiteral("h++")};
+ static const QStringList EXTENSIONS{
+ QStringLiteral("h"),
+ QStringLiteral("H"),
+ QStringLiteral("hpp"),
+ QStringLiteral("hxx"),
+ QStringLiteral("h++")};
+
+ return EXTENSIONS;
}
-static std::vector<MSBuildFilter *> defaultItemGroupFilters(IMSBuildItemGroup *parent = nullptr)
+struct FilterInfo
+{
+ QString name;
+ QList<QString> extensions;
+ bool parseFiles{ true };
+ bool sourceControlFiles{ true };
+};
+
+const std::vector<FilterInfo> & getDefaultFilterInfo()
{
- const auto sourceFilter = new MSBuildFilter(QStringLiteral("Source Files"), sourceFileExtensions(), parent);
- const auto headerFilter = new MSBuildFilter(QStringLiteral("Header Files"), headerFileExtensions(), parent);
-
- const auto formFilter = new MSBuildFilter(QStringLiteral("Form Files"),
- QStringList() << QStringLiteral("ui"), parent);
- const auto resourceFilter = new MSBuildFilter(QStringLiteral("Resource Files"),
- QStringList()
- << QStringLiteral("qrc")
- << QStringLiteral("rc")
- << QStringLiteral("*"), parent);
- resourceFilter->setParseFiles(false);
- const auto generatedFilter = new MSBuildFilter(QStringLiteral("Generated Files"),
- QStringList() << QStringLiteral("moc"), parent);
- generatedFilter->setSourceControlFiles(false);
- const auto translationFilter = new MSBuildFilter(QStringLiteral("Translation Files"),
- QStringList() << QStringLiteral("ts"), parent);
- translationFilter->setParseFiles(false);
-
- return std::vector<MSBuildFilter *> {
- sourceFilter, headerFilter, formFilter, resourceFilter, generatedFilter, translationFilter
+ static const std::vector<FilterInfo> INFOS {
+ {QStringLiteral("Source Files"), sourceFileExtensions()},
+ {QStringLiteral("Header Files"), headerFileExtensions()},
+ {QStringLiteral("Form Files"), QStringList() << QStringLiteral("ui")},
+ {QStringLiteral("Resource Files"), QStringList() << QStringLiteral("qrc") << QStringLiteral("rc") << QStringLiteral("*"), false},
+ {QStringLiteral("Generated Files"), QStringList() << QStringLiteral("moc"), true, false},
+ {QStringLiteral("Translation Files"), QStringList() << QStringLiteral("ts"), false},
};
+
+ return INFOS;
}
-static bool matchesFilter(const MSBuildFilter *filter, const QString &filePath)
+MSBuildFilter * makeBuildFilter(const FilterInfo &filterInfo,
+ MSBuildItemGroup *itemFiltersGroup)
{
- return filter->extensions().contains(QFileInfo(filePath).completeSuffix());
+ const auto filter = new MSBuildFilter(filterInfo.name, filterInfo.extensions, itemFiltersGroup);
+ filter->appendProperty(QStringLiteral("ParseFiles"), filterInfo.parseFiles);
+ filter->appendProperty(QStringLiteral("SourceControlFiles"), filterInfo.sourceControlFiles);
+ return filter;
}
-MSBuildFiltersProject::MSBuildFiltersProject(const GeneratableProductData &product,
- QObject *parent)
- : MSBuildProject(parent)
+bool matchesFilter(const FilterInfo &filterInfo,
+ const QString &filePath)
{
- // Normally this would be versionInfo.toolsVersion() but for some reason it seems
- // filters projects are always v4.0
- setToolsVersion(QStringLiteral("4.0"));
+ return filterInfo.extensions.contains(QFileInfo(filePath).completeSuffix());
+}
- const auto itemGroup = new MSBuildItemGroup(this);
- const auto filterOptions = defaultItemGroupFilters();
- for (const auto options : filterOptions) {
- const auto filter = new MSBuildFilter(options->include(), options->extensions(), itemGroup);
- filter->appendProperty(QStringLiteral("ParseFiles"), options->parseFiles());
- filter->appendProperty(QStringLiteral("SourceControlFiles"), options->sourceControlFiles());
- }
+bool isHeaderFile(const QString &filePath)
+{
+ return headerFileExtensions().contains(QFileInfo(filePath).completeSuffix());
+}
+
+bool isSourceFile(const QString &filePath)
+{
+ return sourceFileExtensions().contains(QFileInfo(filePath).completeSuffix());
+}
+
+MSBuildFileItem * makeFileItem(const QString& filePath,
+ MSBuildItemGroup *itemGroup)
+{
+ if (isHeaderFile(filePath))
+ return new MSBuildClInclude(itemGroup);
+ if (isSourceFile(filePath))
+ return new MSBuildClCompile(itemGroup);
+
+ return new MSBuildNone(itemGroup);
+}
+
+
+class ProductProcessor
+{
+public:
using StringSet = Internal::Set<QString>;
- StringSet allFiles;
- const auto productDatas = product.data.values();
- for (const auto &productData : productDatas) {
- for (const auto &groupData : productData.groups())
- if (groupData.isEnabled())
- allFiles.unite(Internal::rangeTo<StringSet>(groupData.allFilePaths()));
+
+ ProductProcessor(MSBuildProject *parent)
+ : m_parent(parent)
+ , m_itemFiltersGroup(new MSBuildItemGroup(m_parent))
+ {
}
- MSBuildItemGroup *headerFilesGroup = nullptr;
- MSBuildItemGroup *sourceFilesGroup = nullptr;
- MSBuildItemGroup *filesGroup = nullptr;
-
- for (const auto &filePath : allFiles) {
- MSBuildFileItem *fileItem = nullptr;
-
- for (const MSBuildFilter *options : filterOptions) {
- if (matchesFilter(options, filePath)) {
- if (options->include() == QStringLiteral("Header Files")) {
- if (!headerFilesGroup)
- headerFilesGroup = new MSBuildItemGroup(this);
- fileItem = new MSBuildClInclude(headerFilesGroup);
- } else if (options->include() == QStringLiteral("Source Files")) {
- if (!sourceFilesGroup)
- sourceFilesGroup = new MSBuildItemGroup(this);
- fileItem = new MSBuildClCompile(sourceFilesGroup);
+ void operator()(const QList<ProductData> &productDatas)
+ {
+ for (const auto &productData : productDatas) {
+ const auto &productName = productData.name();
+
+ for (const auto &groupData : productData.groups()) {
+ if (groupData.name() == productName) {
+ processProductFiles(Internal::rangeTo<StringSet>(groupData.allFilePaths()));
+ } else {
+ processGroup(groupData);
+ }
+ }
+ }
+ }
+
+ void processProductFiles(const StringSet &files)
+ {
+ for (const auto &filePath : files) {
+ MSBuildFileItem *fileItem = nullptr;
+
+ for (const auto &filterInfo : getDefaultFilterInfo()) {
+ if (matchesFilter(filterInfo, filePath)) {
+ makeFilter(filterInfo);
+
+ if (filterInfo.name == QStringLiteral("Header Files")) {
+ if (!m_headerFilesGroup)
+ m_headerFilesGroup = new MSBuildItemGroup(m_parent);
+ fileItem = new MSBuildClInclude(m_headerFilesGroup);
+ } else if (filterInfo.name == QStringLiteral("Source Files")) {
+ if (!m_sourceFilesGroup)
+ m_sourceFilesGroup = new MSBuildItemGroup(m_parent);
+ fileItem = new MSBuildClCompile(m_sourceFilesGroup);
+ }
+
+ if (fileItem) {
+ fileItem->setFilterName(filterInfo.name);
+ break;
+ }
}
+ }
- if (fileItem) {
- fileItem->setFilterName(options->include());
- break;
+ if (!fileItem) {
+ if (!m_filesGroup) {
+ m_filesGroup = new MSBuildItemGroup(m_parent);
}
+
+ fileItem = new MSBuildNone(m_filesGroup);
}
+
+ fileItem->setFilePath(filePath);
+ }
+ }
+
+ void processGroup(const GroupData &groupData)
+ {
+ makeFilter({groupData.name(), QStringList() << QStringLiteral("*")});
+
+ auto *itemGroup = new MSBuildItemGroup(m_parent);
+ const auto &files = groupData.allFilePaths();
+ for (const auto &filePath : files) {
+ auto *fileItem = makeFileItem(filePath, itemGroup);
+ fileItem->setFilePath(filePath);
+ fileItem->setFilterName(groupData.name());
}
+ }
- if (!fileItem) {
- if (!filesGroup)
- filesGroup = new MSBuildItemGroup(this);
- fileItem = new MSBuildNone(filesGroup);
+ void makeFilter(const FilterInfo &filterInfo)
+ {
+ if (!m_createdFilters.contains(filterInfo.name)) {
+ makeBuildFilter(filterInfo, m_itemFiltersGroup);
+ m_createdFilters.insert(filterInfo.name);
}
- fileItem->setFilePath(filePath);
}
- qDeleteAll(filterOptions);
+private:
+ MSBuildProject *m_parent = nullptr;
+ MSBuildItemGroup *m_itemFiltersGroup = nullptr;
+ MSBuildItemGroup *m_headerFilesGroup = nullptr;
+ MSBuildItemGroup *m_sourceFilesGroup = nullptr;
+ MSBuildItemGroup *m_filesGroup = nullptr;
+ QSet<QString> m_createdFilters;
+};
+
+} // namespace
+
+MSBuildFiltersProject::MSBuildFiltersProject(const GeneratableProductData &product,
+ QObject *parent)
+ : MSBuildProject(parent)
+{
+ // Normally this would be versionInfo.toolsVersion() but for some reason it seems
+ // filters projects are always v4.0
+ setToolsVersion(QStringLiteral("4.0"));
+
+ ProductProcessor(this)(product.data.values());
}
} // namespace qbs
diff --git a/src/plugins/scanner/cpp/Lexer.cpp b/src/plugins/scanner/cpp/Lexer.cpp
index ebf843aca..6793080e9 100644
--- a/src/plugins/scanner/cpp/Lexer.cpp
+++ b/src/plugins/scanner/cpp/Lexer.cpp
@@ -564,7 +564,7 @@ void Lexer::scan_helper(Token *tok)
do {
yyinp();
- if (! (isalnum(_yychar) || _yychar == '_' || _yychar == '$'))
+ if (!isalnum(_yychar) && _yychar != '_' && _yychar != '$')
break;
} while (_yychar);
diff --git a/src/shared/CMakeLists.txt b/src/shared/CMakeLists.txt
index e517f6489..226dd77ec 100644
--- a/src/shared/CMakeLists.txt
+++ b/src/shared/CMakeLists.txt
@@ -1,3 +1,4 @@
add_subdirectory(json)
+add_subdirectory(lsp)
add_subdirectory(quickjs)
add_subdirectory(variant)
diff --git a/src/shared/json/json.cpp b/src/shared/json/json.cpp
index 7ab85cba5..0ea6e8fd0 100644
--- a/src/shared/json/json.cpp
+++ b/src/shared/json/json.cpp
@@ -3757,7 +3757,7 @@ static void valueToJson(const Base *b, const Value &v, std::string &json, int in
// +2 to format to ensure the expected precision
const int n = std::numeric_limits<double>::digits10 + 2;
char buf[30] = {0};
- std::sprintf(buf, "%.*g", n, d);
+ std::snprintf(buf, sizeof(buf), "%.*g", n, d);
// Hack:
if (buf[0] == '-' && buf[1] == '0' && buf[2] == '\0')
json += "0";
diff --git a/src/shared/lsp/.clang-tidy b/src/shared/lsp/.clang-tidy
new file mode 100644
index 000000000..f451e9596
--- /dev/null
+++ b/src/shared/lsp/.clang-tidy
@@ -0,0 +1,3 @@
+Checks: '-*,misc-definitions-in-headers'
+CheckOptions:
+ - { key: HeaderFileExtensions, value: "x" }
diff --git a/src/shared/lsp/CMakeLists.txt b/src/shared/lsp/CMakeLists.txt
new file mode 100644
index 000000000..1ed95db25
--- /dev/null
+++ b/src/shared/lsp/CMakeLists.txt
@@ -0,0 +1,57 @@
+add_qbs_library(qtclsp
+ STATIC
+ DEPENDS Qt${QT_VERSION_MAJOR}::Core Qt6Core5Compat
+ SOURCES
+ algorithm.h
+ basemessage.cpp
+ basemessage.h
+ callhierarchy.cpp
+ callhierarchy.h
+ client.cpp
+ client.h
+ clientcapabilities.cpp
+ clientcapabilities.h
+ completion.cpp
+ completion.h
+ diagnostics.cpp
+ diagnostics.h
+ initializemessages.cpp
+ initializemessages.h
+ jsonkeys.h
+ jsonobject.cpp
+ jsonobject.h
+ jsonrpcmessages.cpp
+ jsonrpcmessages.h
+ languagefeatures.cpp
+ languagefeatures.h
+ languageserverprotocol_global.h
+ languageserverprotocoltr.h
+ lsptypes.cpp
+ lsptypes.h
+ lsputils.cpp
+ lsputils.h
+ messages.cpp
+ messages.h
+ progresssupport.cpp
+ progresssupport.h
+ semantictokens.cpp
+ semantictokens.h
+ servercapabilities.cpp
+ servercapabilities.h
+ shutdownmessages.cpp
+ shutdownmessages.h
+ textsynchronization.cpp
+ textsynchronization.h
+ textutils.cpp
+ textutils.h
+ workspace.cpp
+ workspace.h
+ )
+target_include_directories(
+ qtclsp
+ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../lib/corelib
+ PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..
+)
+target_compile_definitions(qtclsp
+ PUBLIC "LANGUAGESERVERPROTOCOL_STATIC_LIBRARY"
+)
diff --git a/src/shared/lsp/algorithm.h b/src/shared/lsp/algorithm.h
new file mode 100644
index 000000000..4c41fb649
--- /dev/null
+++ b/src/shared/lsp/algorithm.h
@@ -0,0 +1,1518 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "predicates.h"
+
+#include <qcompilerdetection.h> // for Q_REQUIRED_RESULT
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <set>
+#include <tuple>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <QHash>
+#include <QObject>
+#include <QSet>
+#include <QStringList>
+
+#include <memory>
+#include <optional>
+#include <type_traits>
+
+namespace lsp::Utils {
+
+/////////////////////////
+// anyOf
+/////////////////////////
+template<typename T, typename F>
+bool anyOf(const T &container, F predicate);
+template<typename T, typename R, typename S>
+bool anyOf(const T &container, R (S::*predicate)() const);
+template<typename T, typename R, typename S>
+bool anyOf(const T &container, R S::*member);
+
+/////////////////////////
+// count
+/////////////////////////
+template<typename T, typename F>
+int count(const T &container, F predicate);
+
+/////////////////////////
+// allOf
+/////////////////////////
+template<typename T, typename F>
+bool allOf(const T &container, F predicate);
+
+/////////////////////////
+// erase
+/////////////////////////
+template<typename T, typename F>
+void erase(T &container, F predicate);
+template<typename T, typename F>
+bool eraseOne(T &container, F predicate);
+
+/////////////////////////
+// contains
+/////////////////////////
+template<typename T, typename F>
+bool contains(const T &container, F function);
+template<typename T, typename R, typename S>
+bool contains(const T &container, R (S::*function)() const);
+template<typename C, typename R, typename S>
+bool contains(const C &container, R S::*member);
+
+/////////////////////////
+// findOr
+/////////////////////////
+template<typename C, typename F>
+Q_REQUIRED_RESULT typename C::value_type findOr(const C &container,
+ typename C::value_type other,
+ F function);
+template<typename T, typename R, typename S>
+Q_REQUIRED_RESULT typename T::value_type findOr(const T &container,
+ typename T::value_type other,
+ R (S::*function)() const);
+template<typename T, typename R, typename S>
+Q_REQUIRED_RESULT typename T::value_type findOr(const T &container,
+ typename T::value_type other,
+ R S::*member);
+
+/////////////////////////
+// findOrDefault
+/////////////////////////
+template<typename C, typename F>
+Q_REQUIRED_RESULT typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value,
+ typename C::value_type>
+findOrDefault(const C &container, F function);
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value,
+ typename C::value_type>
+findOrDefault(const C &container, R (S::*function)() const);
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value,
+ typename C::value_type>
+findOrDefault(const C &container, R S::*member);
+
+/////////////////////////
+// indexOf
+/////////////////////////
+template<typename C, typename F>
+Q_REQUIRED_RESULT int indexOf(const C &container, F function);
+
+/////////////////////////
+// maxElementOr
+/////////////////////////
+template<typename T>
+typename T::value_type maxElementOr(const T &container, typename T::value_type other);
+
+/////////////////////////
+// filtered
+/////////////////////////
+template<typename C, typename F>
+Q_REQUIRED_RESULT C filtered(const C &container, F predicate);
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT C filtered(const C &container, R (S::*predicate)() const);
+
+/////////////////////////
+// partition
+/////////////////////////
+// Recommended usage:
+// C hit;
+// C miss;
+// std::tie(hit, miss) = Utils::partition(container, predicate);
+template<typename C, typename F>
+Q_REQUIRED_RESULT std::tuple<C, C> partition(const C &container, F predicate);
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT std::tuple<C, C> partition(const C &container, R (S::*predicate)() const);
+
+/////////////////////////
+// filteredUnique
+/////////////////////////
+template<typename C>
+Q_REQUIRED_RESULT C filteredUnique(const C &container);
+
+/////////////////////////
+// qobject_container_cast
+/////////////////////////
+template<class T, template<typename> class Container, typename Base>
+Container<T> qobject_container_cast(const Container<Base> &container);
+
+/////////////////////////
+// static_container_cast
+/////////////////////////
+template<class T, template<typename> class Container, typename Base>
+Container<T> static_container_cast(const Container<Base> &container);
+
+/////////////////////////
+// sort
+/////////////////////////
+template<typename Container>
+inline void sort(Container &container);
+template<typename Container, typename Predicate>
+inline void sort(Container &container, Predicate p);
+template<typename Container, typename R, typename S>
+inline void sort(Container &container, R S::*member);
+template<typename Container, typename R, typename S>
+inline void sort(Container &container, R (S::*function)() const);
+
+/////////////////////////
+// reverseForeach
+/////////////////////////
+template<typename Container, typename Op>
+inline void reverseForeach(const Container &c, const Op &operation);
+
+/////////////////////////
+// toReferences
+/////////////////////////
+template<template<typename...> class ResultContainer, typename SourceContainer>
+auto toReferences(SourceContainer &sources);
+template<typename SourceContainer>
+auto toReferences(SourceContainer &sources);
+
+/////////////////////////
+// toConstReferences
+/////////////////////////
+template<template<typename...> class ResultContainer, typename SourceContainer>
+auto toConstReferences(const SourceContainer &sources);
+template<typename SourceContainer>
+auto toConstReferences(const SourceContainer &sources);
+
+/////////////////////////
+// take
+/////////////////////////
+template<class C, typename P>
+Q_REQUIRED_RESULT std::optional<typename C::value_type> take(C &container, P predicate);
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT decltype(auto) take(C &container, R S::*member);
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT decltype(auto) take(C &container, R (S::*function)() const);
+
+/////////////////////////
+// setUnionMerge
+/////////////////////////
+// Works like std::set_union but provides a merge function for items that match
+// !(a > b) && !(b > a) which normally means that there is an "equal" match.
+// It uses iterators to support move_iterators.
+template<class InputIt1, class InputIt2, class OutputIt, class Merge, class Compare>
+OutputIt setUnionMerge(InputIt1 first1,
+ InputIt1 last1,
+ InputIt2 first2,
+ InputIt2 last2,
+ OutputIt d_first,
+ Merge merge,
+ Compare comp);
+template<class InputIt1, class InputIt2, class OutputIt, class Merge>
+OutputIt setUnionMerge(
+ InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first, Merge merge);
+template<class OutputContainer, class InputContainer1, class InputContainer2, class Merge, class Compare>
+OutputContainer setUnionMerge(InputContainer1 &&input1,
+ InputContainer2 &&input2,
+ Merge merge,
+ Compare comp);
+template<class OutputContainer, class InputContainer1, class InputContainer2, class Merge>
+OutputContainer setUnionMerge(InputContainer1 &&input1, InputContainer2 &&input2, Merge merge);
+
+/////////////////////////
+// setUnion
+/////////////////////////
+template<typename InputIterator1, typename InputIterator2, typename OutputIterator, typename Compare>
+OutputIterator set_union(InputIterator1 first1,
+ InputIterator1 last1,
+ InputIterator2 first2,
+ InputIterator2 last2,
+ OutputIterator result,
+ Compare comp);
+template<typename InputIterator1, typename InputIterator2, typename OutputIterator>
+OutputIterator set_union(InputIterator1 first1,
+ InputIterator1 last1,
+ InputIterator2 first2,
+ InputIterator2 last2,
+ OutputIterator result);
+
+/////////////////////////
+// transform
+/////////////////////////
+// function without result type deduction:
+template<typename ResultContainer, // complete result container type
+ typename SC, // input container type
+ typename F> // function type
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function);
+
+// function with result type deduction:
+template<template<typename> class C, // result container type
+ typename SC, // input container type
+ typename F, // function type
+ typename Value = typename std::decay_t<SC>::value_type,
+ typename Result = std::decay_t<std::result_of_t<F(Value &)>>,
+ typename ResultContainer = C<Result>>
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function);
+#ifdef Q_CC_CLANG
+// "Matching of template template-arguments excludes compatible templates"
+// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0522r0.html (P0522R0)
+// in C++17 makes the above match e.g. C=std::vector even though that takes two
+// template parameters. Unfortunately the following one matches too, and there is no additional
+// partial ordering rule, resulting in an ambiguous call for this previously valid code.
+// GCC and MSVC ignore that issue and follow the standard to the letter, but Clang only
+// enables the new behavior when given -frelaxed-template-template-args .
+// To avoid requiring everyone using this header to enable that feature, keep the old implementation
+// for Clang.
+template<template<typename, typename> class C, // result container type
+ typename SC, // input container type
+ typename F, // function type
+ typename Value = typename std::decay_t<SC>::value_type,
+ typename Result = std::decay_t<std::result_of_t<F(Value &)>>,
+ typename ResultContainer = C<Result, std::allocator<Result>>>
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function);
+#endif
+
+// member function without result type deduction:
+template<template<typename...> class C, // result container type
+ typename SC, // input container type
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, R (S::*p)() const);
+
+// member function with result type deduction:
+template<typename ResultContainer, // complete result container type
+ typename SC, // input container type
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, R (S::*p)() const);
+
+// member without result type deduction:
+template<typename ResultContainer, // complete result container type
+ typename SC, // input container
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, R S::*p);
+
+// member with result type deduction:
+template<template<typename...> class C, // result container
+ typename SC, // input container
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, R S::*p);
+
+// same container types for input and output, const input
+// function:
+template<template<typename...> class C, // container type
+ typename F, // function type
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT decltype(auto) transform(const C<CArgs...> &container, F function);
+
+// same container types for input and output, const input
+// member function:
+template<template<typename...> class C, // container type
+ typename R,
+ typename S,
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT decltype(auto) transform(const C<CArgs...> &container, R (S::*p)() const);
+
+// same container types for input and output, const input
+// members:
+template<template<typename...> class C, // container
+ typename R,
+ typename S,
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT decltype(auto) transform(const C<CArgs...> &container, R S::*p);
+
+// same container types for input and output, non-const input
+// function:
+template<template<typename...> class C, // container type
+ typename F, // function type
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT decltype(auto) transform(C<CArgs...> &container, F function);
+
+// same container types for input and output, non-const input
+// member function:
+template<template<typename...> class C, // container type
+ typename R,
+ typename S,
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT decltype(auto) transform(C<CArgs...> &container, R (S::*p)() const);
+
+// same container types for input and output, non-const input
+// members:
+template<template<typename...> class C, // container
+ typename R,
+ typename S,
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT decltype(auto) transform(C<CArgs...> &container, R S::*p);
+
+/////////////////////////////////////////////////////////////////////////////
+//////// Implementations //////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+//////////////////
+// anyOf
+/////////////////
+template<typename T, typename F>
+bool anyOf(const T &container, F predicate)
+{
+ return std::any_of(std::begin(container), std::end(container), predicate);
+}
+
+// anyOf taking a member function pointer
+template<typename T, typename R, typename S>
+bool anyOf(const T &container, R (S::*predicate)() const)
+{
+ return std::any_of(std::begin(container), std::end(container), std::mem_fn(predicate));
+}
+
+// anyOf taking a member pointer
+template<typename T, typename R, typename S>
+bool anyOf(const T &container, R S::*member)
+{
+ return std::any_of(std::begin(container), std::end(container), std::mem_fn(member));
+}
+
+
+//////////////////
+// count
+/////////////////
+template<typename T, typename F>
+int count(const T &container, F predicate)
+{
+ return std::count_if(std::begin(container), std::end(container), predicate);
+}
+
+//////////////////
+// allOf
+/////////////////
+template<typename T, typename F>
+bool allOf(const T &container, F predicate)
+{
+ return std::all_of(std::begin(container), std::end(container), predicate);
+}
+
+// allOf taking a member function pointer
+template<typename T, typename R, typename S>
+bool allOf(const T &container, R (S::*predicate)() const)
+{
+ return std::all_of(std::begin(container), std::end(container), std::mem_fn(predicate));
+}
+
+// allOf taking a member pointer
+template<typename T, typename R, typename S>
+bool allOf(const T &container, R S::*member)
+{
+ return std::all_of(std::begin(container), std::end(container), std::mem_fn(member));
+}
+
+//////////////////
+// erase
+/////////////////
+template<typename T, typename F>
+void erase(T &container, F predicate)
+{
+ container.erase(std::remove_if(std::begin(container), std::end(container), predicate),
+ std::end(container));
+}
+template<typename T, typename F>
+bool eraseOne(T &container, F predicate)
+{
+ const auto it = std::find_if(std::begin(container), std::end(container), predicate);
+ if (it == std::end(container))
+ return false;
+ container.erase(it);
+ return true;
+}
+
+//////////////////
+// contains
+/////////////////
+template<typename T, typename F>
+bool contains(const T &container, F function)
+{
+ return anyOf(container, function);
+}
+
+template<typename T, typename R, typename S>
+bool contains(const T &container, R (S::*function)() const)
+{
+ return anyOf(container, function);
+}
+
+template<typename C, typename R, typename S>
+bool contains(const C &container, R S::*member)
+{
+ return anyOf(container, std::mem_fn(member));
+}
+
+//////////////////
+// findOr
+/////////////////
+template<typename C, typename F>
+Q_REQUIRED_RESULT
+typename C::value_type findOr(const C &container, typename C::value_type other, F function)
+{
+ typename C::const_iterator begin = std::begin(container);
+ typename C::const_iterator end = std::end(container);
+
+ typename C::const_iterator it = std::find_if(begin, end, function);
+ return it == end ? other : *it;
+}
+
+template<typename T, typename R, typename S>
+Q_REQUIRED_RESULT
+typename T::value_type findOr(const T &container, typename T::value_type other, R (S::*function)() const)
+{
+ return findOr(container, other, std::mem_fn(function));
+}
+
+template<typename T, typename R, typename S>
+Q_REQUIRED_RESULT
+typename T::value_type findOr(const T &container, typename T::value_type other, R S::*member)
+{
+ return findOr(container, other, std::mem_fn(member));
+}
+
+//////////////////
+// findOrDefault
+//////////////////
+// Default implementation:
+template<typename C, typename F>
+Q_REQUIRED_RESULT
+typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, typename C::value_type>
+findOrDefault(const C &container, F function)
+{
+ return findOr(container, typename C::value_type(), function);
+}
+
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT
+typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, typename C::value_type>
+findOrDefault(const C &container, R (S::*function)() const)
+{
+ return findOr(container, typename C::value_type(), std::mem_fn(function));
+}
+
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT
+typename std::enable_if_t<std::is_copy_assignable<typename C::value_type>::value, typename C::value_type>
+findOrDefault(const C &container, R S::*member)
+{
+ return findOr(container, typename C::value_type(), std::mem_fn(member));
+}
+
+//////////////////
+// index of:
+//////////////////
+
+template<typename C, typename F>
+Q_REQUIRED_RESULT
+int indexOf(const C& container, F function)
+{
+ typename C::const_iterator begin = std::begin(container);
+ typename C::const_iterator end = std::end(container);
+
+ typename C::const_iterator it = std::find_if(begin, end, function);
+ return it == end ? -1 : std::distance(begin, it);
+}
+
+
+//////////////////
+// max element
+//////////////////
+
+template<typename T>
+typename T::value_type maxElementOr(const T &container, typename T::value_type other)
+{
+ typename T::const_iterator begin = std::begin(container);
+ typename T::const_iterator end = std::end(container);
+
+ typename T::const_iterator it = std::max_element(begin, end);
+ if (it == end)
+ return other;
+ return *it;
+}
+
+
+//////////////////
+// transform
+/////////////////
+
+namespace {
+/////////////////
+// helper code for transform to use back_inserter and thus push_back for everything
+// and insert for QSet<>
+//
+
+// SetInsertIterator, straight from the standard for insert_iterator
+// just without the additional parameter to insert
+template<class Container>
+class SetInsertIterator
+{
+protected:
+ Container *container;
+
+public:
+ using iterator_category = std::output_iterator_tag;
+ using container_type = Container;
+ explicit SetInsertIterator(Container &x)
+ : container(&x)
+ {}
+ SetInsertIterator<Container> &operator=(const typename Container::value_type &value)
+ {
+ container->insert(value);
+ return *this;
+ }
+ SetInsertIterator<Container> &operator=(typename Container::value_type &&value)
+ {
+ container->insert(std::move(value));
+ return *this;
+ }
+ SetInsertIterator<Container> &operator*() { return *this; }
+ SetInsertIterator<Container> &operator++() { return *this; }
+ SetInsertIterator<Container> operator++(int) { return *this; }
+};
+
+// for QMap / QHash, inserting a std::pair / QPair
+template<class Container>
+class MapInsertIterator
+{
+protected:
+ Container *container;
+
+public:
+ using iterator_category = std::output_iterator_tag;
+ using container_type = Container;
+ explicit MapInsertIterator(Container &x)
+ : container(&x)
+ {}
+ MapInsertIterator<Container> &operator=(
+ const std::pair<const typename Container::key_type, typename Container::mapped_type> &value)
+ { container->insert(value.first, value.second); return *this; }
+ MapInsertIterator<Container> &operator=(
+ const QPair<typename Container::key_type, typename Container::mapped_type> &value)
+ { container->insert(value.first, value.second); return *this; }
+ MapInsertIterator<Container> &operator*() { return *this; }
+ MapInsertIterator<Container> &operator++() { return *this; }
+ MapInsertIterator<Container> operator++(int) { return *this; }
+};
+
+// because Qt container are not implementing the standard interface we need
+// this helper functions for generic code
+template<typename Type>
+void append(QList<Type> *container, QList<Type> &&input)
+{
+ container->append(std::move(input));
+}
+
+template<typename Type>
+void append(QList<Type> *container, const QList<Type> &input)
+{
+ container->append(input);
+}
+
+template<typename Container>
+void append(Container *container, Container &&input)
+{
+ container->insert(container->end(),
+ std::make_move_iterator(input.begin()),
+ std::make_move_iterator(input.end()));
+}
+
+template<typename Container>
+void append(Container *container, const Container &input)
+{
+ container->insert(container->end(), input.begin(), input.end());
+}
+
+// BackInsertIterator behaves like std::back_insert_iterator except is adds the back insertion for
+// container of the same type
+template<typename Container>
+class BackInsertIterator
+{
+public:
+ using iterator_category = std::output_iterator_tag;
+ using value_type = void;
+ using difference_type = ptrdiff_t;
+ using pointer = void;
+ using reference = void;
+ using container_type = Container;
+
+ explicit constexpr BackInsertIterator(Container &container)
+ : m_container(std::addressof(container))
+ {}
+
+ constexpr BackInsertIterator &operator=(const typename Container::value_type &value)
+ {
+ m_container->push_back(value);
+ return *this;
+ }
+
+ constexpr BackInsertIterator &operator=(typename Container::value_type &&value)
+ {
+ m_container->push_back(std::move(value));
+ return *this;
+ }
+
+ constexpr BackInsertIterator &operator=(const Container &container)
+ {
+ append(m_container, container);
+ return *this;
+ }
+
+ constexpr BackInsertIterator &operator=(Container &&container)
+ {
+ append(m_container, container);
+ return *this;
+ }
+
+ [[nodiscard]] constexpr BackInsertIterator &operator*() { return *this; }
+
+ constexpr BackInsertIterator &operator++() { return *this; }
+
+ constexpr BackInsertIterator operator++(int) { return *this; }
+
+private:
+ Container *m_container;
+};
+
+// inserter helper function, returns a BackInsertIterator for most containers
+// and is overloaded for QSet<> and other containers without push_back, returning custom inserters
+template<typename Container>
+inline BackInsertIterator<Container> inserter(Container &container)
+{
+ return BackInsertIterator(container);
+}
+
+template<typename X>
+inline SetInsertIterator<QSet<X>>
+inserter(QSet<X> &container)
+{
+ return SetInsertIterator<QSet<X>>(container);
+}
+
+template<typename K, typename C, typename A>
+inline SetInsertIterator<std::set<K, C, A>>
+inserter(std::set<K, C, A> &container)
+{
+ return SetInsertIterator<std::set<K, C, A>>(container);
+}
+
+template<typename K, typename H, typename C, typename A>
+inline SetInsertIterator<std::unordered_set<K, H, C, A>>
+inserter(std::unordered_set<K, H, C, A> &container)
+{
+ return SetInsertIterator<std::unordered_set<K, H, C, A>>(container);
+}
+
+template<typename K, typename V, typename C, typename A>
+inline SetInsertIterator<std::map<K, V, C, A>>
+inserter(std::map<K, V, C, A> &container)
+{
+ return SetInsertIterator<std::map<K, V, C, A>>(container);
+}
+
+template<typename K, typename V, typename H, typename C, typename A>
+inline SetInsertIterator<std::unordered_map<K, V, H, C, A>>
+inserter(std::unordered_map<K, V, H, C, A> &container)
+{
+ return SetInsertIterator<std::unordered_map<K, V, H, C, A>>(container);
+}
+
+template<typename K, typename V>
+inline MapInsertIterator<QMap<K, V>>
+inserter(QMap<K, V> &container)
+{
+ return MapInsertIterator<QMap<K, V>>(container);
+}
+
+template<typename K, typename V>
+inline MapInsertIterator<QHash<K, V>>
+inserter(QHash<K, V> &container)
+{
+ return MapInsertIterator<QHash<K, V>>(container);
+}
+
+// Helper code for container.reserve that makes it possible to effectively disable it for
+// specific cases
+
+// default: do reserve
+// Template arguments are more specific than the second version below, so this is tried first
+template<template<typename...> class C, typename... CArgs,
+ typename = decltype(&C<CArgs...>::reserve)>
+void reserve(C<CArgs...> &c, typename C<CArgs...>::size_type s)
+{
+ c.reserve(s);
+}
+
+// containers that don't have reserve()
+template<typename C>
+void reserve(C &, typename C::size_type) { }
+
+} // anonymous
+
+// --------------------------------------------------------------------
+// Different containers for input and output:
+// --------------------------------------------------------------------
+
+// different container types for input and output, e.g. transforming a QList into a QSet
+
+// function without result type deduction:
+template<typename ResultContainer, // complete result container type
+ typename SC, // input container type
+ typename F> // function type
+Q_REQUIRED_RESULT
+decltype(auto) transform(SC &&container, F function)
+{
+ ResultContainer result;
+ reserve(result, typename ResultContainer::size_type(container.size()));
+ std::transform(std::begin(container), std::end(container), inserter(result), function);
+ return result;
+}
+
+// function with result type deduction:
+template<template<typename> class C, // result container type
+ typename SC, // input container type
+ typename F, // function type
+ typename Value,
+ typename Result,
+ typename ResultContainer>
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function)
+{
+ return transform<ResultContainer>(std::forward<SC>(container), function);
+}
+
+#ifdef Q_CC_CLANG
+template<template<typename, typename> class C, // result container type
+ typename SC, // input container type
+ typename F, // function type
+ typename Value,
+ typename Result,
+ typename ResultContainer>
+Q_REQUIRED_RESULT decltype(auto) transform(SC &&container, F function)
+{
+ return transform<ResultContainer>(std::forward<SC>(container), function);
+}
+#endif
+
+// member function without result type deduction:
+template<template<typename...> class C, // result container type
+ typename SC, // input container type
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT
+decltype(auto) transform(SC &&container, R (S::*p)() const)
+{
+ return transform<C>(std::forward<SC>(container), std::mem_fn(p));
+}
+
+// member function with result type deduction:
+template<typename ResultContainer, // complete result container type
+ typename SC, // input container type
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT
+decltype(auto) transform(SC &&container, R (S::*p)() const)
+{
+ return transform<ResultContainer>(std::forward<SC>(container), std::mem_fn(p));
+}
+
+// member without result type deduction:
+template<typename ResultContainer, // complete result container type
+ typename SC, // input container
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT
+decltype(auto) transform(SC &&container, R S::*p)
+{
+ return transform<ResultContainer>(std::forward<SC>(container), std::mem_fn(p));
+}
+
+// member with result type deduction:
+template<template<typename...> class C, // result container
+ typename SC, // input container
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT
+decltype(auto) transform(SC &&container, R S::*p)
+{
+ return transform<C>(std::forward<SC>(container), std::mem_fn(p));
+}
+
+// same container types for input and output, const input
+
+// function:
+template<template<typename...> class C, // container type
+ typename F, // function type
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT
+decltype(auto) transform(const C<CArgs...> &container, F function)
+{
+ return transform<C, const C<CArgs...> &>(container, function);
+}
+
+// member function:
+template<template<typename...> class C, // container type
+ typename R,
+ typename S,
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT
+decltype(auto) transform(const C<CArgs...> &container, R (S::*p)() const)
+{
+ return transform<C, const C<CArgs...> &>(container, std::mem_fn(p));
+}
+
+// members:
+template<template<typename...> class C, // container
+ typename R,
+ typename S,
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT
+decltype(auto) transform(const C<CArgs...> &container, R S::*p)
+{
+ return transform<C, const C<CArgs...> &>(container, std::mem_fn(p));
+}
+
+// same container types for input and output, non-const input
+
+// function:
+template<template<typename...> class C, // container type
+ typename F, // function type
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT
+decltype(auto) transform(C<CArgs...> &container, F function)
+{
+ return transform<C, C<CArgs...> &>(container, function);
+}
+
+// member function:
+template<template<typename...> class C, // container type
+ typename R,
+ typename S,
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT
+decltype(auto) transform(C<CArgs...> &container, R (S::*p)() const)
+{
+ return transform<C, C<CArgs...> &>(container, std::mem_fn(p));
+}
+
+// members:
+template<template<typename...> class C, // container
+ typename R,
+ typename S,
+ typename... CArgs> // Arguments to SC
+Q_REQUIRED_RESULT
+decltype(auto) transform(C<CArgs...> &container, R S::*p)
+{
+ return transform<C, C<CArgs...> &>(container, std::mem_fn(p));
+}
+
+// Specialization for QStringList:
+
+template<template<typename...> class C = QList, // result container
+ typename F> // Arguments to C
+Q_REQUIRED_RESULT
+decltype(auto) transform(const QStringList &container, F function)
+{
+ return transform<C, const QList<QString> &>(static_cast<QList<QString>>(container), function);
+}
+
+// member function:
+template<template<typename...> class C = QList, // result container type
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT
+decltype(auto) transform(const QStringList &container, R (S::*p)() const)
+{
+ return transform<C, const QList<QString> &>(static_cast<QList<QString>>(container), std::mem_fn(p));
+}
+
+// members:
+template<template<typename...> class C = QList, // result container
+ typename R,
+ typename S>
+Q_REQUIRED_RESULT
+decltype(auto) transform(const QStringList &container, R S::*p)
+{
+ return transform<C, const QList<QString> &>(static_cast<QList<QString>>(container), std::mem_fn(p));
+}
+
+//////////////////
+// filtered
+/////////////////
+template<typename C, typename F>
+Q_REQUIRED_RESULT
+C filtered(const C &container, F predicate)
+{
+ C out;
+ std::copy_if(std::begin(container), std::end(container),
+ inserter(out), predicate);
+ return out;
+}
+
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT
+C filtered(const C &container, R (S::*predicate)() const)
+{
+ C out;
+ std::copy_if(std::begin(container), std::end(container),
+ inserter(out), std::mem_fn(predicate));
+ return out;
+}
+
+//////////////////
+// filteredCast
+/////////////////
+template<typename R, typename C, typename F>
+Q_REQUIRED_RESULT R filteredCast(const C &container, F predicate)
+{
+ R out;
+ std::copy_if(std::begin(container), std::end(container), inserter(out), predicate);
+ return out;
+}
+
+//////////////////
+// partition
+/////////////////
+
+// Recommended usage:
+// C hit;
+// C miss;
+// std::tie(hit, miss) = Utils::partition(container, predicate);
+
+template<typename C, typename F>
+Q_REQUIRED_RESULT
+std::tuple<C, C> partition(const C &container, F predicate)
+{
+ C hit;
+ C miss;
+ reserve(hit, container.size());
+ reserve(miss, container.size());
+ auto hitIns = inserter(hit);
+ auto missIns = inserter(miss);
+ for (const auto &i : container) {
+ if (predicate(i))
+ hitIns = i;
+ else
+ missIns = i;
+ }
+ return std::make_tuple(hit, miss);
+}
+
+template<typename C, typename R, typename S>
+Q_REQUIRED_RESULT
+std::tuple<C, C> partition(const C &container, R (S::*predicate)() const)
+{
+ return partition(container, std::mem_fn(predicate));
+}
+
+//////////////////
+// filteredUnique
+/////////////////
+
+template<typename C>
+Q_REQUIRED_RESULT
+C filteredUnique(const C &container)
+{
+ C result;
+ auto ins = inserter(result);
+
+ QSet<typename C::value_type> seen;
+ int setSize = 0;
+
+ auto endIt = std::end(container);
+ for (auto it = std::begin(container); it != endIt; ++it) {
+ seen.insert(*it);
+ if (setSize == seen.size()) // unchanged size => was already seen
+ continue;
+ ++setSize;
+ ins = *it;
+ }
+ return result;
+}
+
+//////////////////
+// qobject_container_cast
+/////////////////
+template <class T, template<typename> class Container, typename Base>
+Container<T> qobject_container_cast(const Container<Base> &container)
+{
+ Container<T> result;
+ auto ins = inserter(result);
+ for (Base val : container) {
+ if (T target = qobject_cast<T>(val))
+ ins = target;
+ }
+ return result;
+}
+
+//////////////////
+// static_container_cast
+/////////////////
+template <class T, template<typename> class Container, typename Base>
+Container<T> static_container_cast(const Container<Base> &container)
+{
+ Container<T> result;
+ reserve(result, container.size());
+ auto ins = inserter(result);
+ for (Base val : container)
+ ins = static_cast<T>(val);
+ return result;
+}
+
+//////////////////
+// sort
+/////////////////
+template <typename Container>
+inline void sort(Container &container)
+{
+ std::stable_sort(std::begin(container), std::end(container));
+}
+
+template <typename Container, typename Predicate>
+inline void sort(Container &container, Predicate p)
+{
+ std::stable_sort(std::begin(container), std::end(container), p);
+}
+
+// const lvalue
+template<typename Container>
+inline Container sorted(const Container &container)
+{
+ Container c = container;
+ sort(c);
+ return c;
+}
+
+// non-const lvalue
+// This is needed because otherwise the "universal" reference below is used, modifying the input
+// container.
+template<typename Container>
+inline Container sorted(Container &container)
+{
+ Container c = container;
+ sort(c);
+ return c;
+}
+
+// non-const rvalue (actually rvalue or lvalue, but lvalue is handled above)
+template<typename Container>
+inline Container sorted(Container &&container)
+{
+ sort(container);
+ return std::move(container);
+}
+
+// const rvalue
+template<typename Container>
+inline Container sorted(const Container &&container)
+{
+ return sorted(container);
+}
+
+// const lvalue
+template<typename Container, typename Predicate>
+inline Container sorted(const Container &container, Predicate p)
+{
+ Container c = container;
+ sort(c, p);
+ return c;
+}
+
+// non-const lvalue
+// This is needed because otherwise the "universal" reference below is used, modifying the input
+// container.
+template<typename Container, typename Predicate>
+inline Container sorted(Container &container, Predicate p)
+{
+ Container c = container;
+ sort(c, p);
+ return c;
+}
+
+// non-const rvalue (actually rvalue or lvalue, but lvalue is handled above)
+template<typename Container, typename Predicate>
+inline Container sorted(Container &&container, Predicate p)
+{
+ sort(container, p);
+ return std::move(container);
+}
+
+// const rvalue
+template<typename Container, typename Predicate>
+inline Container sorted(const Container &&container, Predicate p)
+{
+ return sorted(container, p);
+}
+
+// pointer to member
+template<typename Container, typename R, typename S>
+inline void sort(Container &container, R S::*member)
+{
+ auto f = std::mem_fn(member);
+ using const_ref = typename Container::const_reference;
+ std::stable_sort(std::begin(container), std::end(container),
+ [&f](const_ref a, const_ref b) {
+ return f(a) < f(b);
+ });
+}
+
+// const lvalue
+template<typename Container, typename R, typename S>
+inline Container sorted(const Container &container, R S::*member)
+{
+ Container c = container;
+ sort(c, member);
+ return c;
+}
+
+// non-const lvalue
+// This is needed because otherwise the "universal" reference below is used, modifying the input
+// container.
+template<typename Container, typename R, typename S>
+inline Container sorted(Container &container, R S::*member)
+{
+ Container c = container;
+ sort(c, member);
+ return c;
+}
+
+// non-const rvalue (actually rvalue or lvalue, but lvalue is handled above)
+template<typename Container, typename R, typename S>
+inline Container sorted(Container &&container, R S::*member)
+{
+ sort(container, member);
+ return std::move(container);
+}
+
+// const rvalue
+template<typename Container, typename R, typename S>
+inline Container sorted(const Container &&container, R S::*member)
+{
+ return sorted(container, member);
+}
+
+// pointer to member function
+template<typename Container, typename R, typename S>
+inline void sort(Container &container, R (S::*function)() const)
+{
+ auto f = std::mem_fn(function);
+ using const_ref = typename Container::const_reference;
+ std::stable_sort(std::begin(container), std::end(container),
+ [&f](const_ref a, const_ref b) {
+ return f(a) < f(b);
+ });
+}
+
+// const lvalue
+template<typename Container, typename R, typename S>
+inline Container sorted(const Container &container, R (S::*function)() const)
+{
+ Container c = container;
+ sort(c, function);
+ return c;
+}
+
+// non-const lvalue
+// This is needed because otherwise the "universal" reference below is used, modifying the input
+// container.
+template<typename Container, typename R, typename S>
+inline Container sorted(Container &container, R (S::*function)() const)
+{
+ Container c = container;
+ sort(c, function);
+ return c;
+}
+
+// non-const rvalue (actually rvalue or lvalue, but lvalue is handled above)
+template<typename Container, typename R, typename S>
+inline Container sorted(Container &&container, R (S::*function)() const)
+{
+ sort(container, function);
+ return std::move(container);
+}
+
+// const rvalue
+template<typename Container, typename R, typename S>
+inline Container sorted(const Container &&container, R (S::*function)() const)
+{
+ return sorted(container, function);
+}
+
+//////////////////
+// reverseForeach
+/////////////////
+template <typename Container, typename Op>
+inline void reverseForeach(const Container &c, const Op &operation)
+{
+ auto rend = c.rend();
+ for (auto it = c.rbegin(); it != rend; ++it)
+ operation(*it);
+}
+
+//////////////////
+// toReferences
+/////////////////
+template <template<typename...> class ResultContainer,
+ typename SourceContainer>
+auto toReferences(SourceContainer &sources)
+{
+ return transform<ResultContainer>(sources, [] (auto &value) { return std::ref(value); });
+}
+
+template <typename SourceContainer>
+auto toReferences(SourceContainer &sources)
+{
+ return transform(sources, [] (auto &value) { return std::ref(value); });
+}
+
+//////////////////
+// toConstReferences
+/////////////////
+template <template<typename...> class ResultContainer,
+ typename SourceContainer>
+auto toConstReferences(const SourceContainer &sources)
+{
+ return transform<ResultContainer>(sources, [] (const auto &value) { return std::cref(value); });
+}
+
+template <typename SourceContainer>
+auto toConstReferences(const SourceContainer &sources)
+{
+ return transform(sources, [] (const auto &value) { return std::cref(value); });
+}
+
+//////////////////
+// take:
+/////////////////
+
+template<class C, typename P>
+Q_REQUIRED_RESULT std::optional<typename C::value_type> take(C &container, P predicate)
+{
+ const auto end = std::end(container);
+
+ const auto it = std::find_if(std::begin(container), end, predicate);
+ if (it == end)
+ return std::nullopt;
+
+ std::optional<typename C::value_type> result = std::make_optional(std::move(*it));
+ container.erase(it);
+ return result;
+}
+
+// pointer to member
+template <typename C, typename R, typename S>
+Q_REQUIRED_RESULT decltype(auto) take(C &container, R S::*member)
+{
+ return take(container, std::mem_fn(member));
+}
+
+// pointer to member function
+template <typename C, typename R, typename S>
+Q_REQUIRED_RESULT decltype(auto) take(C &container, R (S::*function)() const)
+{
+ return take(container, std::mem_fn(function));
+}
+
+//////////////////
+// setUnionMerge: Works like std::set_union but provides a merge function for items that match
+// !(a > b) && !(b > a) which normally means that there is an "equal" match.
+// It uses iterators to support move_iterators.
+/////////////////
+
+template<class InputIt1,
+ class InputIt2,
+ class OutputIt,
+ class Merge,
+ class Compare>
+OutputIt setUnionMerge(InputIt1 first1,
+ InputIt1 last1,
+ InputIt2 first2,
+ InputIt2 last2,
+ OutputIt d_first,
+ Merge merge,
+ Compare comp)
+{
+ for (; first1 != last1; ++d_first) {
+ if (first2 == last2)
+ return std::copy(first1, last1, d_first);
+ if (comp(*first2, *first1)) {
+ *d_first = *first2++;
+ } else {
+ if (comp(*first1, *first2)) {
+ *d_first = *first1;
+ } else {
+ *d_first = merge(*first1, *first2);
+ ++first2;
+ }
+ ++first1;
+ }
+ }
+ return std::copy(first2, last2, d_first);
+}
+
+template<class InputIt1,
+ class InputIt2,
+ class OutputIt,
+ class Merge>
+OutputIt setUnionMerge(InputIt1 first1,
+ InputIt1 last1,
+ InputIt2 first2,
+ InputIt2 last2,
+ OutputIt d_first,
+ Merge merge)
+{
+ return setUnionMerge(first1,
+ last1,
+ first2,
+ last2,
+ d_first,
+ merge,
+ std::less<std::decay_t<decltype(*first1)>>{});
+}
+
+template<class OutputContainer,
+ class InputContainer1,
+ class InputContainer2,
+ class Merge,
+ class Compare>
+OutputContainer setUnionMerge(InputContainer1 &&input1,
+ InputContainer2 &&input2,
+ Merge merge,
+ Compare comp)
+{
+ OutputContainer results;
+ results.reserve(input1.size() + input2.size());
+
+ setUnionMerge(std::make_move_iterator(std::begin(input1)),
+ std::make_move_iterator(std::end(input1)),
+ std::make_move_iterator(std::begin(input2)),
+ std::make_move_iterator(std::end(input2)),
+ std::back_inserter(results),
+ merge,
+ comp);
+
+ return results;
+}
+
+template<class OutputContainer,
+ class InputContainer1,
+ class InputContainer2,
+ class Merge>
+OutputContainer setUnionMerge(InputContainer1 &&input1,
+ InputContainer2 &&input2,
+ Merge merge)
+{
+ return setUnionMerge<OutputContainer>(std::forward<InputContainer1>(input1),
+ std::forward<InputContainer2>(input2),
+ merge,
+ std::less<std::decay_t<decltype(*std::begin(input1))>>{});
+}
+
+template<typename Container>
+auto usize(const Container &container)
+{
+ return static_cast<std::make_unsigned_t<decltype(std::size(container))>>(std::size(container));
+}
+
+template<typename Container>
+auto ssize(const Container &container)
+{
+ return static_cast<std::make_signed_t<decltype(std::size(container))>>(std::size(container));
+}
+
+template<typename Compare>
+struct CompareIter
+{
+ Compare compare;
+
+ explicit constexpr CompareIter(Compare compare)
+ : compare(std::move(compare))
+ {}
+
+ template<typename Iterator1, typename Iterator2>
+ constexpr bool operator()(Iterator1 it1, Iterator2 it2)
+ {
+ return bool(compare(*it1, *it2));
+ }
+};
+
+template<typename InputIterator1, typename InputIterator2, typename OutputIterator, typename Compare>
+OutputIterator set_union_impl(InputIterator1 first1,
+ InputIterator1 last1,
+ InputIterator2 first2,
+ InputIterator2 last2,
+ OutputIterator result,
+ Compare comp)
+{
+ auto compare = CompareIter<Compare>(comp);
+
+ while (first1 != last1 && first2 != last2) {
+ if (compare(first1, first2)) {
+ *result = *first1;
+ ++first1;
+ } else if (compare(first2, first1)) {
+ *result = *first2;
+ ++first2;
+ } else {
+ *result = *first1;
+ ++first1;
+ ++first2;
+ }
+ ++result;
+ }
+
+ return std::copy(first2, last2, std::copy(first1, last1, result));
+}
+
+template<typename InputIterator1, typename InputIterator2, typename OutputIterator, typename Compare>
+OutputIterator set_union(InputIterator1 first1,
+ InputIterator1 last1,
+ InputIterator2 first2,
+ InputIterator2 last2,
+ OutputIterator result,
+ Compare comp)
+{
+ return set_union_impl(first1, last1, first2, last2, result, comp);
+}
+
+template<typename InputIterator1, typename InputIterator2, typename OutputIterator>
+OutputIterator set_union(InputIterator1 first1,
+ InputIterator1 last1,
+ InputIterator2 first2,
+ InputIterator2 last2,
+ OutputIterator result)
+{
+ return set_union_impl(
+ first1, last1, first2, last2, result, std::less<typename InputIterator1::value_type>{});
+}
+
+// Replacement for deprecated Qt functionality
+
+template <class T>
+QSet<T> toSet(const QList<T> &list)
+{
+ 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 <class Key, class T>
+void addToHash(QHash<Key, T> *result, const QHash<Key, T> &additionalContents)
+{
+ result->insert(additionalContents);
+}
+
+// Workaround for missing information from QSet::insert()
+// Return type could be a pair like for std::set, but we never use the iterator anyway.
+template<typename T, typename U> [[nodiscard]] bool insert(QSet<T> &s, const U &v)
+{
+ const int oldSize = s.size();
+ s.insert(v);
+ return s.size() > oldSize;
+}
+
+} // namespace lsp::Utils
diff --git a/src/shared/lsp/basemessage.cpp b/src/shared/lsp/basemessage.cpp
new file mode 100644
index 000000000..4b19190dc
--- /dev/null
+++ b/src/shared/lsp/basemessage.cpp
@@ -0,0 +1,188 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "basemessage.h"
+
+#include "jsonrpcmessages.h"
+#include "languageserverprotocoltr.h"
+
+#include <QBuffer>
+#include <QTextCodec>
+
+#include <cstring>
+#include <utility>
+
+namespace lsp {
+
+Q_LOGGING_CATEGORY(parseLog, "qtc.languageserverprotocol.parse", QtWarningMsg)
+
+constexpr char headerFieldSeparator[] = ": ";
+constexpr char contentCharsetName[] = "charset";
+constexpr char defaultCharset[] = "utf-8";
+constexpr char contentLengthFieldName[] = "Content-Length";
+constexpr char headerSeparator[] = "\r\n";
+constexpr char contentTypeFieldName[] = "Content-Type";
+
+BaseMessage::BaseMessage()
+ : mimeType(JsonRpcMessage::jsonRpcMimeType())
+{ }
+
+BaseMessage::BaseMessage(const QByteArray &mimeType, const QByteArray &content,
+ int expectedLength, QTextCodec *codec)
+ : mimeType(mimeType.isEmpty() ? JsonRpcMessage::jsonRpcMimeType() : mimeType)
+ , content(content)
+ , contentLength(expectedLength)
+ , codec(codec)
+{ }
+
+BaseMessage::BaseMessage(const QByteArray &mimeType, const QByteArray &content)
+ : BaseMessage(mimeType, content, content.length(), defaultCodec())
+{ }
+
+bool BaseMessage::operator==(const BaseMessage &other) const
+{
+ if (mimeType != other.mimeType || content != other.content)
+ return false;
+ if (codec) {
+ if (other.codec)
+ return codec->mibEnum() == other.codec->mibEnum();
+ return codec->mibEnum() == defaultCodec()->mibEnum();
+ }
+ if (other.codec)
+ return other.codec->mibEnum() == defaultCodec()->mibEnum();
+
+ return true;
+}
+
+static QPair<QByteArray, QByteArray> splitHeaderFieldLine(const QByteArray &headerFieldLine)
+{
+ static const int fieldSeparatorLength = int(std::strlen(headerFieldSeparator));
+ int assignmentIndex = headerFieldLine.indexOf(headerFieldSeparator);
+ if (assignmentIndex >= 0) {
+ return {headerFieldLine.mid(0, assignmentIndex),
+ headerFieldLine.mid(assignmentIndex + fieldSeparatorLength)};
+ }
+ qCWarning(parseLog) << "Unexpected header line:" << QLatin1String(headerFieldLine);
+ return {};
+}
+
+static void parseContentType(BaseMessage &message, QByteArray contentType, QString &parseError)
+{
+ if (contentType.startsWith('"') && contentType.endsWith('"'))
+ contentType = contentType.mid(1, contentType.length() - 2);
+ QList<QByteArray> contentTypeElements = contentType.split(';');
+ QByteArray mimeTypeName = contentTypeElements.takeFirst();
+ QTextCodec *codec = nullptr;
+ for (const QByteArray &_contentTypeElement : contentTypeElements) {
+ const QByteArray &contentTypeElement = _contentTypeElement.trimmed();
+ if (contentTypeElement.startsWith(contentCharsetName)) {
+ const int equalindex = contentTypeElement.indexOf('=');
+ const QByteArray charset = contentTypeElement.mid(equalindex + 1);
+ if (equalindex > 0)
+ codec = QTextCodec::codecForName(charset);
+ if (!codec) {
+ parseError = Tr::tr("Cannot decode content with \"%1\". Falling back to \"%2\".")
+ .arg(QLatin1String(charset),
+ QLatin1String(defaultCharset));
+ }
+ }
+ }
+ message.mimeType = mimeTypeName;
+ message.codec = codec ? codec : BaseMessage::defaultCodec();
+}
+
+static void parseContentLength(BaseMessage &message, QByteArray contentLength, QString &parseError)
+{
+ bool ok = true;
+ message.contentLength = contentLength.toInt(&ok);
+ if (!ok) {
+ parseError = Tr::tr("Expected an integer in \"%1\", but got \"%2\".")
+ .arg(QString::fromLatin1(contentLengthFieldName), QString::fromLatin1(contentLength));
+ }
+}
+
+void BaseMessage::parse(QBuffer *data, QString &parseError, BaseMessage &message)
+{
+ const qint64 startPos = data->pos();
+
+ if (message.isValid()) { // incomplete message from last parse
+ message.content.append(data->read(message.contentLength - message.content.length()));
+ return;
+ }
+
+ while (!data->atEnd()) {
+ const QByteArray &headerFieldLine = data->readLine();
+ if (headerFieldLine == headerSeparator) {
+ if (message.isValid())
+ message.content = data->read(message.contentLength);
+ return;
+ }
+ const QPair<QByteArray, QByteArray> nameAndValue = splitHeaderFieldLine(headerFieldLine);
+ const QByteArray &headerFieldName = nameAndValue.first.trimmed();
+ const QByteArray &headerFieldValue = nameAndValue.second.trimmed();
+
+ if (headerFieldName.isEmpty())
+ continue;
+ if (headerFieldName == contentLengthFieldName) {
+ parseContentLength(message, headerFieldValue, parseError);
+ } else if (headerFieldName == contentTypeFieldName) {
+ parseContentType(message, headerFieldValue, parseError);
+ } else {
+ qCWarning(parseLog) << "Unexpected header field" << QLatin1String(headerFieldName)
+ << "in" << QLatin1String(headerFieldLine);
+ }
+ }
+
+ // the complete header wasn't received jet, waiting for the rest of it and reparse
+ message = BaseMessage();
+ data->seek(startPos);
+}
+
+QTextCodec *BaseMessage::defaultCodec()
+{
+ static QTextCodec *codec = QTextCodec::codecForName(defaultCharset);
+ return codec;
+}
+
+bool BaseMessage::isComplete() const
+{
+ if (!isValid())
+ return false;
+ QBS_ASSERT(content.length() <= contentLength, return true);
+ return content.length() == contentLength;
+}
+
+bool BaseMessage::isValid() const
+{
+ return contentLength >= 0;
+}
+
+QByteArray BaseMessage::header() const
+{
+ QByteArray header;
+ header.append(lengthHeader());
+ if (codec != defaultCodec()
+ || (!mimeType.isEmpty() && mimeType != JsonRpcMessage::jsonRpcMimeType())) {
+ header.append(typeHeader());
+ }
+ header.append(headerSeparator);
+ return header;
+}
+
+QByteArray BaseMessage::lengthHeader() const
+{
+ return QByteArray(contentLengthFieldName)
+ + QByteArray(headerFieldSeparator)
+ + QString::number(content.size()).toLatin1()
+ + QByteArray(headerSeparator);
+}
+
+QByteArray BaseMessage::typeHeader() const
+{
+ return QByteArray(contentTypeFieldName)
+ + QByteArray(headerFieldSeparator)
+ + mimeType + "; " + contentCharsetName + "=" + codec->name()
+ + QByteArray(headerSeparator);
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/basemessage.h b/src/shared/lsp/basemessage.h
new file mode 100644
index 000000000..4c6f0cb48
--- /dev/null
+++ b/src/shared/lsp/basemessage.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "languageserverprotocol_global.h"
+
+#include <QByteArray>
+#include <QCoreApplication>
+#include <QLoggingCategory>
+
+QT_BEGIN_NAMESPACE
+class QBuffer;
+class QTextCodec;
+QT_END_NAMESPACE
+
+namespace lsp {
+
+LANGUAGESERVERPROTOCOL_EXPORT Q_DECLARE_LOGGING_CATEGORY(parseLog)
+
+class LANGUAGESERVERPROTOCOL_EXPORT BaseMessage
+{
+public:
+ BaseMessage();
+ BaseMessage(const QByteArray &mimeType, const QByteArray &content,
+ int expectedLength, QTextCodec *codec);
+ BaseMessage(const QByteArray &mimeType, const QByteArray &content);
+
+ bool operator==(const BaseMessage &other) const;
+
+ static void parse(QBuffer *data, QString &parseError, BaseMessage &message);
+ static QTextCodec *defaultCodec();
+
+ bool isComplete() const;
+ bool isValid() const;
+ QByteArray header() const;
+
+ QByteArray mimeType;
+ QByteArray content;
+ int contentLength = -1;
+ QTextCodec *codec = defaultCodec();
+
+private:
+ QByteArray lengthHeader() const;
+ QByteArray typeHeader() const;
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/callhierarchy.cpp b/src/shared/lsp/callhierarchy.cpp
new file mode 100644
index 000000000..af786fa95
--- /dev/null
+++ b/src/shared/lsp/callhierarchy.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "callhierarchy.h"
+
+namespace lsp {
+
+bool CallHierarchyItem::isValid() const
+{
+ return contains(nameKey) && contains(symbolKindKey) && contains(rangeKey) && contains(uriKey)
+ && contains(selectionRangeKey);
+}
+
+PrepareCallHierarchyRequest::PrepareCallHierarchyRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{}
+
+CallHierarchyIncomingCallsRequest::CallHierarchyIncomingCallsRequest(
+ const CallHierarchyCallsParams &params)
+ : Request(methodName, params)
+{}
+
+CallHierarchyOutgoingCallsRequest::CallHierarchyOutgoingCallsRequest(
+ const CallHierarchyCallsParams &params)
+ : Request(methodName, params)
+{}
+
+} // namespace lsp
diff --git a/src/shared/lsp/callhierarchy.h b/src/shared/lsp/callhierarchy.h
new file mode 100644
index 000000000..3db1f08de
--- /dev/null
+++ b/src/shared/lsp/callhierarchy.h
@@ -0,0 +1,108 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyItem : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString name() const { return typedValue<QString>(nameKey); }
+ void setName(const QString &name) { insert(nameKey, name); }
+
+ SymbolKind symbolKind() const { return SymbolKind(typedValue<int>(symbolKindKey)); }
+ void setSymbolKind(const SymbolKind &symbolKind) { insert(symbolKindKey, int(symbolKind)); }
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ Range selectionRange() const { return typedValue<Range>(selectionRangeKey); }
+ void setSelectionRange(Range selectionRange) { insert(selectionRangeKey, selectionRange); }
+
+ std::optional<QString> detail() const { return optionalValue<QString>(detailKey); }
+ void setDetail(const QString &detail) { insert(detailKey, detail); }
+ void clearDetail() { remove(detailKey); }
+
+ std::optional<QList<DocumentSymbol>> children() const
+ { return optionalArray<DocumentSymbol>(childrenKey); }
+ void setChildren(const QList<DocumentSymbol> &children) { insertArray(childrenKey, children); }
+ void clearChildren() { remove(childrenKey); }
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT PrepareCallHierarchyRequest : public Request<
+ LanguageClientArray<CallHierarchyItem>, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit PrepareCallHierarchyRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/prepareCallHierarchy";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyCallsParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ CallHierarchyItem item() const { return typedValue<CallHierarchyItem>(itemKey); }
+ void setItem(const CallHierarchyItem &item) { insert(itemKey, item); }
+
+ bool isValid() const override { return contains(itemKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyIncomingCall : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ CallHierarchyItem from() const { return typedValue<CallHierarchyItem>(fromKey); }
+ void setFrom(const CallHierarchyItem &from) { insert(fromKey, from); }
+
+ QList<Range> fromRanges() const { return array<Range>(fromRangesKey); }
+ void setFromRanges(const QList<Range> &fromRanges) { insertArray(fromRangesKey, fromRanges); }
+
+ bool isValid() const override { return contains(fromRangesKey) && contains(fromRangesKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyIncomingCallsRequest : public Request<
+ LanguageClientArray<CallHierarchyIncomingCall>, std::nullptr_t, CallHierarchyCallsParams>
+{
+public:
+ explicit CallHierarchyIncomingCallsRequest(const CallHierarchyCallsParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "callHierarchy/incomingCalls";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyOutgoingCall : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ CallHierarchyItem to() const { return typedValue<CallHierarchyItem>(toKey); }
+ void setTo(const CallHierarchyItem &to) { insert(toKey, to); }
+
+ QList<Range> fromRanges() const { return array<Range>(fromRangesKey); }
+ void setFromRanges(const QList<Range> &fromRanges) { insertArray(fromRangesKey, fromRanges); }
+
+ bool isValid() const override { return contains(fromRangesKey) && contains(fromRangesKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CallHierarchyOutgoingCallsRequest : public Request<
+ LanguageClientArray<CallHierarchyOutgoingCall>, std::nullptr_t, CallHierarchyCallsParams>
+{
+public:
+ explicit CallHierarchyOutgoingCallsRequest(const CallHierarchyCallsParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "callHierarchy/outgoingCalls";
+};
+
+} // namespace lsp
diff --git a/src/shared/lsp/client.cpp b/src/shared/lsp/client.cpp
new file mode 100644
index 000000000..8479131a6
--- /dev/null
+++ b/src/shared/lsp/client.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "client.h"
+
+namespace lsp {
+
+constexpr const char RegisterCapabilityRequest::methodName[];
+constexpr const char UnregisterCapabilityRequest::methodName[];
+
+RegisterCapabilityRequest::RegisterCapabilityRequest(const RegistrationParams &params)
+ : Request(methodName, params) { }
+
+UnregisterCapabilityRequest::UnregisterCapabilityRequest(const UnregistrationParams &params)
+ : UnregisterCapabilityRequest(methodName, params) { }
+
+} // namespace lsp
diff --git a/src/shared/lsp/client.h b/src/shared/lsp/client.h
new file mode 100644
index 000000000..29ebc5bc6
--- /dev/null
+++ b/src/shared/lsp/client.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+
+namespace lsp {
+
+class Registration : public JsonObject
+{
+public:
+ Registration() : Registration(QString()) {}
+ explicit Registration(const QString &method)
+ {
+ setId(QUuid::createUuid().toString());
+ setMethod(method);
+ }
+ using JsonObject::JsonObject;
+
+ QString id() const { return typedValue<QString>(idKey); }
+ void setId(const QString &id) { insert(idKey, id); }
+
+ QString method() const { return typedValue<QString>(methodKey); }
+ void setMethod(const QString &method) { insert(methodKey, method); }
+
+ QJsonValue registerOptions() const { return value(registerOptionsKey); }
+ void setRegisterOptions(const QJsonValue &registerOptions)
+ { insert(registerOptionsKey, registerOptions); }
+
+ bool isValid() const override { return contains(idKey) && contains(methodKey); }
+};
+
+class RegistrationParams : public JsonObject
+{
+public:
+ RegistrationParams() : RegistrationParams(QList<Registration>()) {}
+ explicit RegistrationParams(const QList<Registration> &registrations)
+ { setRegistrations(registrations); }
+ using JsonObject::JsonObject;
+
+ QList<Registration> registrations() const { return array<Registration>(registrationsKey); }
+ void setRegistrations(const QList<Registration> &registrations)
+ { insertArray(registrationsKey, registrations); }
+
+ bool isValid() const override { return contains(registrationsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT RegisterCapabilityRequest : public Request<
+ std::nullptr_t, std::nullptr_t, RegistrationParams>
+{
+public:
+ explicit RegisterCapabilityRequest(const RegistrationParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "client/registerCapability";
+};
+
+class Unregistration : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString id() const { return typedValue<QString>(idKey); }
+ void setId(const QString &id) { insert(idKey, id); }
+
+ QString method() const { return typedValue<QString>(methodKey); }
+ void setMethod(const QString &method) { insert(methodKey, method); }
+
+ bool isValid() const override { return contains(idKey) && contains(methodKey); }
+};
+
+class UnregistrationParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QList<Unregistration> unregistrations() const
+ { return array<Unregistration>(unregistrationsKey); }
+ void setUnregistrations(const QList<Unregistration> &unregistrations)
+ { insertArray(unregistrationsKey, unregistrations); }
+
+ bool isValid() const override { return contains(unregistrationsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT UnregisterCapabilityRequest : public Request<
+ std::nullptr_t, std::nullptr_t, UnregistrationParams>
+{
+public:
+ explicit UnregisterCapabilityRequest(const UnregistrationParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "client/unregisterCapability";
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/clientcapabilities.cpp b/src/shared/lsp/clientcapabilities.cpp
new file mode 100644
index 000000000..34d9de9e7
--- /dev/null
+++ b/src/shared/lsp/clientcapabilities.cpp
@@ -0,0 +1,122 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clientcapabilities.h"
+
+namespace lsp {
+
+std::optional<QList<SymbolKind>> SymbolCapabilities::SymbolKindCapabilities::valueSet() const
+{
+ if (std::optional<QList<int>> array = optionalArray<int>(valueSetKey)) {
+ return std::make_optional(
+ Utils::transform(*array, [](int value) { return static_cast<SymbolKind>(value); }));
+ }
+ return std::nullopt;
+}
+
+void SymbolCapabilities::SymbolKindCapabilities::setValueSet(const QList<SymbolKind> &valueSet)
+{
+ insert(valueSetKey, enumArrayToJsonArray<SymbolKind>(valueSet));
+}
+
+WorkspaceClientCapabilities::WorkspaceClientCapabilities()
+{
+ setWorkspaceFolders(true);
+}
+
+std::optional<std::variant<bool, QJsonObject>> SemanticTokensClientCapabilities::Requests::range()
+ const
+{
+ using RetType = std::variant<bool, QJsonObject>;
+ const QJsonValue &rangeOptions = value(rangeKey);
+ if (rangeOptions.isBool())
+ return RetType(rangeOptions.toBool());
+ if (rangeOptions.isObject())
+ return RetType(rangeOptions.toObject());
+ return std::nullopt;
+}
+
+void SemanticTokensClientCapabilities::Requests::setRange(
+ const std::variant<bool, QJsonObject> &range)
+{
+ insertVariant<bool, QJsonObject>(rangeKey, range);
+}
+
+std::optional<std::variant<bool, FullSemanticTokenOptions>>
+SemanticTokensClientCapabilities::Requests::full() const
+{
+ using RetType = std::variant<bool, FullSemanticTokenOptions>;
+ const QJsonValue &fullOptions = value(fullKey);
+ if (fullOptions.isBool())
+ return RetType(fullOptions.toBool());
+ if (fullOptions.isObject())
+ return RetType(FullSemanticTokenOptions(fullOptions.toObject()));
+ return std::nullopt;
+}
+
+void SemanticTokensClientCapabilities::Requests::setFull(
+ const std::variant<bool, FullSemanticTokenOptions> &full)
+{
+ insertVariant<bool, FullSemanticTokenOptions>(fullKey, full);
+}
+
+std::optional<SemanticTokensClientCapabilities> TextDocumentClientCapabilities::semanticTokens() const
+{
+ return optionalValue<SemanticTokensClientCapabilities>(semanticTokensKey);
+}
+
+void TextDocumentClientCapabilities::setSemanticTokens(
+ const SemanticTokensClientCapabilities &semanticTokens)
+{
+ insert(semanticTokensKey, semanticTokens);
+}
+
+bool SemanticTokensClientCapabilities::isValid() const
+{
+ return contains(requestsKey) && contains(tokenTypesKey) && contains(tokenModifiersKey)
+ && contains(formatsKey);
+}
+
+const char resourceOperationCreate[] = "create";
+const char resourceOperationRename[] = "rename";
+const char resourceOperationDelete[] = "delete";
+
+std::optional<QList<WorkspaceClientCapabilities::WorkspaceEditCapabilities::ResourceOperationKind>>
+WorkspaceClientCapabilities::WorkspaceEditCapabilities::resourceOperations() const
+{
+ if (!contains(resourceOperationsKey))
+ return std::nullopt;
+ QList<ResourceOperationKind> result;
+ for (const QJsonValue &value : this->value(resourceOperationsKey).toArray()) {
+ const QString str = value.toString();
+ if (str == resourceOperationCreate)
+ result << ResourceOperationKind::Create;
+ else if (str == resourceOperationRename)
+ result << ResourceOperationKind::Rename;
+ else if (str == resourceOperationDelete)
+ result << ResourceOperationKind::Delete;
+ }
+ return result;
+}
+
+void WorkspaceClientCapabilities::WorkspaceEditCapabilities::setResourceOperations(
+ const QList<ResourceOperationKind> &resourceOperations)
+{
+ QJsonArray array;
+ for (const auto &kind : resourceOperations) {
+ switch (kind) {
+ case ResourceOperationKind::Create:
+ array << resourceOperationCreate;
+ break;
+ case ResourceOperationKind::Rename:
+ array << resourceOperationRename;
+ break;
+ case ResourceOperationKind::Delete:
+ array << resourceOperationDelete;
+ break;
+ }
+ }
+ insert(resourceOperationsKey, array);
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/clientcapabilities.h b/src/shared/lsp/clientcapabilities.h
new file mode 100644
index 000000000..44add0a2c
--- /dev/null
+++ b/src/shared/lsp/clientcapabilities.h
@@ -0,0 +1,674 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonkeys.h"
+#include "lsptypes.h"
+#include "semantictokens.h"
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT DynamicRegistrationCapabilities : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ std::optional<bool> dynamicRegistration() const
+ {
+ return optionalValue<bool>(dynamicRegistrationKey);
+ }
+ void setDynamicRegistration(bool dynamicRegistration) { insert(dynamicRegistrationKey, dynamicRegistration); }
+ void clearDynamicRegistration() { remove(dynamicRegistrationKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT FullSemanticTokenOptions : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /**
+ * The client will send the `textDocument/semanticTokens/full/delta`
+ * request if the server provides a corresponding handler.
+ */
+ std::optional<bool> delta() const { return optionalValue<bool>(deltaKey); }
+ void setDelta(bool delta) { insert(deltaKey, delta); }
+ void clearDelta() { remove(deltaKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensClientCapabilities : public DynamicRegistrationCapabilities
+{
+public:
+ using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities;
+ class LANGUAGESERVERPROTOCOL_EXPORT Requests : public JsonObject
+ {
+ /**
+ * Which requests the client supports and might send to the server
+ * depending on the server's capability. Please note that clients might not
+ * show semantic tokens or degrade some of the user experience if a range
+ * or full request is advertised by the client but not provided by the
+ * server. If for example the client capability `requests.full` and
+ * `request.range` are both set to true but the server only provides a
+ * range provider the client might not render a minimap correctly or might
+ * even decide to not show any semantic tokens at all.
+ */
+ public:
+ using JsonObject::JsonObject;
+
+ /**
+ * The client will send the `textDocument/semanticTokens/range` request
+ * if the server provides a corresponding handler.
+ */
+ std::optional<std::variant<bool, QJsonObject>> range() const;
+ void setRange(const std::variant<bool, QJsonObject> &range);
+ void clearRange() { remove(rangeKey); }
+
+ /**
+ * The client will send the `textDocument/semanticTokens/full` request
+ * if the server provides a corresponding handler.
+ */
+ std::optional<std::variant<bool, FullSemanticTokenOptions>> full() const;
+ void setFull(const std::variant<bool, FullSemanticTokenOptions> &full);
+ void clearFull() { remove(fullKey); }
+ };
+
+ Requests requests() const { return typedValue<Requests>(requestsKey); }
+ void setRequests(const Requests &requests) { insert(requestsKey, requests); }
+
+ /// The token types that the client supports.
+ QList<QString> tokenTypes() const { return array<QString>(tokenTypesKey); }
+ void setTokenTypes(const QList<QString> &value) { insertArray(tokenTypesKey, value); }
+
+ /// The token modifiers that the client supports.
+ QList<QString> tokenModifiers() const { return array<QString>(tokenModifiersKey); }
+ void setTokenModifiers(const QList<QString> &value) { insertArray(tokenModifiersKey, value); }
+
+ /// The formats the clients supports.
+ QList<QString> formats() const { return array<QString>(formatsKey); }
+ void setFormats(const QList<QString> &value) { insertArray(formatsKey, value); }
+
+ /// Whether the client supports tokens that can overlap each other.
+ std::optional<bool> overlappingTokenSupport() const
+ {
+ return optionalValue<bool>(overlappingTokenSupportKey);
+ }
+ void setOverlappingTokenSupport(bool overlappingTokenSupport) { insert(overlappingTokenSupportKey, overlappingTokenSupport); }
+ void clearOverlappingTokenSupport() { remove(overlappingTokenSupportKey); }
+
+ /// Whether the client supports tokens that can span multiple lines.
+ std::optional<bool> multiLineTokenSupport() const
+ {
+ return optionalValue<bool>(multiLineTokenSupportKey);
+ }
+ void setMultiLineTokenSupport(bool multiLineTokenSupport) { insert(multiLineTokenSupportKey, multiLineTokenSupport); }
+ void clearMultiLineTokenSupport() { remove(multiLineTokenSupportKey); }
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SymbolCapabilities : public DynamicRegistrationCapabilities
+{
+public:
+ using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT SymbolKindCapabilities : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ /*
+ * The symbol kind values the client supports. When this
+ * property exists the client also guarantees that it will
+ * handle values outside its set gracefully and falls back
+ * to a default value when unknown.
+ *
+ * If this property is not present the client only supports
+ * the symbol kinds from `File` to `Array` as defined in
+ * the initial version of the protocol.
+ */
+ std::optional<QList<SymbolKind>> valueSet() const;
+ void setValueSet(const QList<SymbolKind> &valueSet);
+ void clearValueSet() { remove(valueSetKey); }
+ };
+
+ // Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
+ std::optional<SymbolKindCapabilities> symbolKind() const
+ { return optionalValue<SymbolKindCapabilities>(symbolKindKey); }
+ void setSymbolKind(const SymbolKindCapabilities &symbolKind) { insert(symbolKindKey, symbolKind); }
+ void clearSymbolKind() { remove(symbolKindKey); }
+
+ std::optional<bool> hierarchicalDocumentSymbolSupport() const
+ { return optionalValue<bool>(hierarchicalDocumentSymbolSupportKey); }
+ void setHierarchicalDocumentSymbolSupport(bool hierarchicalDocumentSymbolSupport)
+ { insert(hierarchicalDocumentSymbolSupportKey, hierarchicalDocumentSymbolSupport); }
+ void clearHierachicalDocumentSymbolSupport() { remove(hierarchicalDocumentSymbolSupportKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentClientCapabilities : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT SynchronizationCapabilities : public DynamicRegistrationCapabilities
+ {
+ public:
+ using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities;
+
+ // The client supports sending will save notifications.
+ std::optional<bool> willSave() const { return optionalValue<bool>(willSaveKey); }
+ void setWillSave(bool willSave) { insert(willSaveKey, willSave); }
+ void clearWillSave() { remove(willSaveKey); }
+
+ /*
+ * The client supports sending a will save request and
+ * waits for a response providing text edits which will
+ * be applied to the document before it is saved.
+ */
+ std::optional<bool> willSaveWaitUntil() const
+ { return optionalValue<bool>(willSaveWaitUntilKey); }
+ void setWillSaveWaitUntil(bool willSaveWaitUntil)
+ { insert(willSaveWaitUntilKey, willSaveWaitUntil); }
+ void clearWillSaveWaitUntil() { remove(willSaveWaitUntilKey); }
+
+ // The client supports did save notifications.
+ std::optional<bool> didSave() const { return optionalValue<bool>(didSaveKey); }
+ void setDidSave(bool didSave) { insert(didSaveKey, didSave); }
+ void clearDidSave() { remove(didSaveKey); }
+ };
+
+ std::optional<SynchronizationCapabilities> synchronization() const
+ { return optionalValue<SynchronizationCapabilities>(synchronizationKey); }
+ void setSynchronization(const SynchronizationCapabilities &synchronization)
+ { insert(synchronizationKey, synchronization); }
+ void clearSynchronization() { remove(synchronizationKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT CompletionCapabilities : public DynamicRegistrationCapabilities
+ {
+ public:
+ using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT CompletionItemCapbilities : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ /*
+ * Client supports snippets as insert text.
+ *
+ * A snippet can define tab stops and placeholders with `$1`, `$2`
+ * and `${3:foo}`. `$0` defines the final tab stop, it defaults to
+ * the end of the snippet. Placeholders with equal identifiers are linked,
+ * that is typing in one will update others too.
+ */
+ std::optional<bool> snippetSupport() const
+ { return optionalValue<bool>(snippetSupportKey); }
+ void setSnippetSupport(bool snippetSupport)
+ { insert(snippetSupportKey, snippetSupport); }
+ void clearSnippetSupport() { remove(snippetSupportKey); }
+
+ // Client supports commit characters on a completion item.
+ std::optional<bool> commitCharacterSupport() const
+ { return optionalValue<bool>(commitCharacterSupportKey); }
+ void setCommitCharacterSupport(bool commitCharacterSupport)
+ { insert(commitCharacterSupportKey, commitCharacterSupport); }
+ void clearCommitCharacterSupport() { remove(commitCharacterSupportKey); }
+
+ /*
+ * Client supports the follow content formats for the documentation
+ * property. The order describes the preferred format of the client.
+ */
+ std::optional<QList<MarkupKind>> documentationFormat() const;
+ void setDocumentationFormat(const QList<MarkupKind> &documentationFormat);
+ void clearDocumentationFormat() { remove(documentationFormatKey); }
+ };
+
+ // The client supports the following `CompletionItem` specific capabilities.
+ std::optional<CompletionItemCapbilities> completionItem() const
+ { return optionalValue<CompletionItemCapbilities>(completionItemKey); }
+ void setCompletionItem(const CompletionItemCapbilities &completionItem)
+ { insert(completionItemKey, completionItem); }
+ void clearCompletionItem() { remove(completionItemKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT CompletionItemKindCapabilities : public JsonObject
+ {
+ public:
+ CompletionItemKindCapabilities();
+ using JsonObject::JsonObject;
+ /*
+ * The completion item kind values the client supports. When this
+ * property exists the client also guarantees that it will
+ * handle values outside its set gracefully and falls back
+ * to a default value when unknown.
+ *
+ * If this property is not present the client only supports
+ * the completion items kinds from `Text` to `Reference` as defined in
+ * the initial version of the protocol.
+ */
+ std::optional<QList<CompletionItemKind::Kind>> valueSet() const;
+ void setValueSet(const QList<CompletionItemKind::Kind> &valueSet);
+ void clearValueSet() { remove(valueSetKey); }
+ };
+
+ std::optional<CompletionItemKindCapabilities> completionItemKind() const
+ { return optionalValue<CompletionItemKindCapabilities>(completionItemKindKey); }
+ void setCompletionItemKind(const CompletionItemKindCapabilities &completionItemKind)
+ { insert(completionItemKindKey, completionItemKind); }
+ void clearCompletionItemKind() { remove(completionItemKindKey); }
+
+ /*
+ * The client supports to send additional context information for a
+ * `textDocument/completion` request.
+ */
+ std::optional<bool> contextSupport() const
+ {
+ return optionalValue<bool>(contextSupportKey);
+ }
+ void setContextSupport(bool contextSupport) { insert(contextSupportKey, contextSupport); }
+ void clearContextSupport() { remove(contextSupportKey); }
+ };
+
+ // Capabilities specific to the `textDocument/completion`
+ std::optional<CompletionCapabilities> completion() const
+ { return optionalValue<CompletionCapabilities>(completionKey); }
+ void setCompletion(const CompletionCapabilities &completion)
+ { insert(completionKey, completion); }
+ void clearCompletion() { remove(completionKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT HoverCapabilities : public DynamicRegistrationCapabilities
+ {
+ public:
+ using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities;
+ /*
+ * Client supports the follow content formats for the content
+ * property. The order describes the preferred format of the client.
+ */
+ std::optional<QList<MarkupKind>> contentFormat() const;
+ void setContentFormat(const QList<MarkupKind> &contentFormat);
+ void clearContentFormat() { remove(contentFormatKey); }
+ };
+
+ std::optional<HoverCapabilities> hover() const
+ {
+ return optionalValue<HoverCapabilities>(hoverKey);
+ }
+ void setHover(const HoverCapabilities &hover) { insert(hoverKey, hover); }
+ void clearHover() { remove(hoverKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelpCapabilities : public DynamicRegistrationCapabilities
+ {
+ public:
+ using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT SignatureInformationCapabilities : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+ /*
+ * Client supports the follow content formats for the documentation
+ * property. The order describes the preferred format of the client.
+ */
+ std::optional<QList<MarkupKind>> documentationFormat() const;
+ void setDocumentationFormat(const QList<MarkupKind> &documentationFormat);
+ void clearDocumentationFormat() { remove(documentationFormatKey); }
+
+ std::optional<bool> activeParameterSupport() const
+ { return optionalValue<bool>(activeParameterSupportKey); }
+ void setActiveParameterSupport(bool activeParameterSupport)
+ { insert(activeParameterSupportKey, activeParameterSupport); }
+ void clearActiveParameterSupport() { remove(activeParameterSupportKey); }
+ };
+
+ // The client supports the following `SignatureInformation` specific properties.
+ std::optional<SignatureInformationCapabilities> signatureInformation() const
+ { return optionalValue<SignatureInformationCapabilities>(signatureInformationKey); }
+ void setSignatureInformation(const SignatureInformationCapabilities &signatureInformation)
+ { insert(signatureInformationKey, signatureInformation); }
+ void clearSignatureInformation() { remove(signatureInformationKey); }
+ };
+
+ // Capabilities specific to the `textDocument/signatureHelp`
+ std::optional<SignatureHelpCapabilities> signatureHelp() const
+ { return optionalValue<SignatureHelpCapabilities>(signatureHelpKey); }
+ void setSignatureHelp(const SignatureHelpCapabilities &signatureHelp)
+ { insert(signatureHelpKey, signatureHelp); }
+ void clearSignatureHelp() { remove(signatureHelpKey); }
+
+ // Whether references supports dynamic registration.
+ std::optional<DynamicRegistrationCapabilities> references() const
+ { return optionalValue<DynamicRegistrationCapabilities>(referencesKey); }
+ void setReferences(const DynamicRegistrationCapabilities &references)
+ { insert(referencesKey, references); }
+ void clearReferences() { remove(referencesKey); }
+
+ // Whether document highlight supports dynamic registration.
+ std::optional<DynamicRegistrationCapabilities> documentHighlight() const
+ { return optionalValue<DynamicRegistrationCapabilities>(documentHighlightKey); }
+ void setDocumentHighlight(const DynamicRegistrationCapabilities &documentHighlight)
+ { insert(documentHighlightKey, documentHighlight); }
+ void clearDocumentHighlight() { remove(documentHighlightKey); }
+
+ // Capabilities specific to the `textDocument/documentSymbol`
+ std::optional<SymbolCapabilities> documentSymbol() const
+ { return optionalValue<SymbolCapabilities>(documentSymbolKey); }
+ void setDocumentSymbol(const SymbolCapabilities &documentSymbol)
+ { insert(documentSymbolKey, documentSymbol); }
+ void clearDocumentSymbol() { remove(documentSymbolKey); }
+
+ // Whether formatting supports dynamic registration.
+ std::optional<DynamicRegistrationCapabilities> formatting() const
+ { return optionalValue<DynamicRegistrationCapabilities>(formattingKey); }
+ void setFormatting(const DynamicRegistrationCapabilities &formatting)
+ { insert(formattingKey, formatting); }
+ void clearFormatting() { remove(formattingKey); }
+
+ // Whether range formatting supports dynamic registration.
+ std::optional<DynamicRegistrationCapabilities> rangeFormatting() const
+ { return optionalValue<DynamicRegistrationCapabilities>(rangeFormattingKey); }
+ void setRangeFormatting(const DynamicRegistrationCapabilities &rangeFormatting)
+ { insert(rangeFormattingKey, rangeFormatting); }
+ void clearRangeFormatting() { remove(rangeFormattingKey); }
+
+ // Whether on type formatting supports dynamic registration.
+ std::optional<DynamicRegistrationCapabilities> onTypeFormatting() const
+ { return optionalValue<DynamicRegistrationCapabilities>(onTypeFormattingKey); }
+ void setOnTypeFormatting(const DynamicRegistrationCapabilities &onTypeFormatting)
+ { insert(onTypeFormattingKey, onTypeFormatting); }
+ void clearOnTypeFormatting() { remove(onTypeFormattingKey); }
+
+ // Whether definition supports dynamic registration.
+ std::optional<DynamicRegistrationCapabilities> definition() const
+ { return optionalValue<DynamicRegistrationCapabilities>(definitionKey); }
+ void setDefinition(const DynamicRegistrationCapabilities &definition)
+ { insert(definitionKey, definition); }
+ void clearDefinition() { remove(definitionKey); }
+
+ /*
+ * Whether typeDefinition supports dynamic registration. If this is set to `true`
+ * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+ * return value for the corresponding server capability as well.
+ */
+ std::optional<DynamicRegistrationCapabilities> typeDefinition() const
+ { return optionalValue<DynamicRegistrationCapabilities>(typeDefinitionKey); }
+ void setTypeDefinition(const DynamicRegistrationCapabilities &typeDefinition)
+ { insert(typeDefinitionKey, typeDefinition); }
+ void clearTypeDefinition() { remove(typeDefinitionKey); }
+
+ /*
+ * Whether implementation supports dynamic registration. If this is set to `true`
+ * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+ * return value for the corresponding server capability as well.
+ */
+ std::optional<DynamicRegistrationCapabilities> implementation() const
+ { return optionalValue<DynamicRegistrationCapabilities>(implementationKey); }
+ void setImplementation(const DynamicRegistrationCapabilities &implementation)
+ { insert(implementationKey, implementation); }
+ void clearImplementation() { remove(implementationKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT CodeActionCapabilities : public DynamicRegistrationCapabilities
+ {
+ public:
+ using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT CodeActionLiteralSupport : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT CodeActionKind : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+ CodeActionKind() : CodeActionKind(QList<QString>()) {}
+ explicit CodeActionKind(const QList<QString> &kinds) { setValueSet(kinds); }
+
+ QList<QString> valueSet() const { return array<QString>(valueSetKey); }
+ void setValueSet(const QList<QString> &valueSet)
+ { insertArray(valueSetKey, valueSet); }
+
+ bool isValid() const override { return contains(valueSetKey); }
+ };
+
+ CodeActionKind codeActionKind() const
+ { return typedValue<CodeActionKind>(codeActionKindKey); }
+ void setCodeActionKind(const CodeActionKind &codeActionKind)
+ { insert(codeActionKindKey, codeActionKind); }
+
+ bool isValid() const override { return contains(codeActionKindKey); }
+ };
+
+ std::optional<CodeActionLiteralSupport> codeActionLiteralSupport() const
+ { return optionalValue<CodeActionLiteralSupport>(codeActionLiteralSupportKey); }
+ void setCodeActionLiteralSupport(const CodeActionLiteralSupport &codeActionLiteralSupport)
+ { insert(codeActionLiteralSupportKey, codeActionLiteralSupport); }
+ void clearCodeActionLiteralSupport() { remove(codeActionLiteralSupportKey); }
+ };
+
+ // Whether code action supports dynamic registration.
+ std::optional<CodeActionCapabilities> codeAction() const
+ { return optionalValue<CodeActionCapabilities>(codeActionKey); }
+ void setCodeAction(const CodeActionCapabilities &codeAction)
+ { insert(codeActionKey, codeAction); }
+ void clearCodeAction() { remove(codeActionKey); }
+
+ // Whether code lens supports dynamic registration.
+ std::optional<DynamicRegistrationCapabilities> codeLens() const
+ { return optionalValue<DynamicRegistrationCapabilities>(codeLensKey); }
+ void setCodeLens(const DynamicRegistrationCapabilities &codeLens)
+ { insert(codeLensKey, codeLens); }
+ void clearCodeLens() { remove(codeLensKey); }
+
+ // Whether document link supports dynamic registration.
+ std::optional<DynamicRegistrationCapabilities> documentLink() const
+ { return optionalValue<DynamicRegistrationCapabilities>(documentLinkKey); }
+ void setDocumentLink(const DynamicRegistrationCapabilities &documentLink)
+ { insert(documentLinkKey, documentLink); }
+ void clearDocumentLink() { remove(documentLinkKey); }
+
+ /*
+ * Whether colorProvider supports dynamic registration. If this is set to `true`
+ * the client supports the new `(ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+ * return value for the corresponding server capability as well.
+ */
+ std::optional<DynamicRegistrationCapabilities> colorProvider() const
+ { return optionalValue<DynamicRegistrationCapabilities>(colorProviderKey); }
+ void setColorProvider(const DynamicRegistrationCapabilities &colorProvider)
+ { insert(colorProviderKey, colorProvider); }
+ void clearColorProvider() { remove(colorProviderKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT RenameClientCapabilities : public DynamicRegistrationCapabilities
+ {
+ public:
+ using DynamicRegistrationCapabilities::DynamicRegistrationCapabilities;
+ /**
+ * Client supports testing for validity of rename operations
+ * before execution.
+ *
+ * @since version 3.12.0
+ */
+
+ std::optional<bool> prepareSupport() const
+ {
+ return optionalValue<bool>(prepareSupportKey);
+ }
+ void setPrepareSupport(bool prepareSupport) { insert(prepareSupportKey, prepareSupport); }
+ void clearPrepareSupport() { remove(prepareSupportKey); }
+ };
+
+ // Whether rename supports dynamic registration.
+ std::optional<RenameClientCapabilities> rename() const
+ { return optionalValue<RenameClientCapabilities>(renameKey); }
+ void setRename(const RenameClientCapabilities &rename)
+ { insert(renameKey, rename); }
+ void clearRename() { remove(renameKey); }
+
+ std::optional<SemanticTokensClientCapabilities> semanticTokens() const;
+ void setSemanticTokens(const SemanticTokensClientCapabilities &semanticTokens);
+ void clearSemanticTokens() { remove(semanticTokensKey); }
+
+ std::optional<DynamicRegistrationCapabilities> callHierarchy() const
+ { return optionalValue<DynamicRegistrationCapabilities>(callHierarchyKey); }
+ void setCallHierarchy(const DynamicRegistrationCapabilities &callHierarchy)
+ { insert(callHierarchyKey, callHierarchy); }
+ void clearCallHierarchy() { remove(callHierarchyKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensWorkspaceClientCapabilities : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+ /**
+ * Whether the client implementation supports a refresh request sent from
+ * the server to the client.
+ *
+ * Note that this event is global and will force the client to refresh all
+ * semantic tokens currently shown. It should be used with absolute care
+ * and is useful for situation where a server for example detect a project
+ * wide change that requires such a calculation.
+ */
+ std::optional<bool> refreshSupport() const { return optionalValue<bool>(refreshSupportKey); }
+ void setRefreshSupport(bool refreshSupport) { insert(refreshSupportKey, refreshSupport); }
+ void clearRefreshSupport() { remove(refreshSupportKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceClientCapabilities : public JsonObject
+{
+public:
+ WorkspaceClientCapabilities();
+ using JsonObject::JsonObject;
+
+ /*
+ * The client supports applying batch edits to the workspace by supporting the request
+ * 'workspace/applyEdit'
+ */
+ std::optional<bool> applyEdit() const { return optionalValue<bool>(applyEditKey); }
+ void setApplyEdit(bool applyEdit) { insert(applyEditKey, applyEdit); }
+ void clearApplyEdit() { remove(applyEditKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceEditCapabilities : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ // The client supports versioned document changes in `WorkspaceEdit`s
+ std::optional<bool> documentChanges() const
+ { return optionalValue<bool>(documentChangesKey); }
+ void setDocumentChanges(bool documentChanges)
+ { insert(documentChangesKey, documentChanges); }
+ void clearDocumentChanges() { remove(documentChangesKey); }
+
+ enum class ResourceOperationKind { Create, Rename, Delete };
+
+ // The resource operations the client supports. Clients should at least support 'create',
+ // 'rename' and 'delete' files and folders.
+ std::optional<QList<ResourceOperationKind>> resourceOperations() const;
+ void setResourceOperations(const QList<ResourceOperationKind> &resourceOperations);
+ void clearResourceOperations() { remove(resourceOperationsKey); }
+ };
+
+ // Capabilities specific to `WorkspaceEdit`s
+ std::optional<WorkspaceEditCapabilities> workspaceEdit() const
+ { return optionalValue<WorkspaceEditCapabilities>(workspaceEditKey); }
+ void setWorkspaceEdit(const WorkspaceEditCapabilities &workspaceEdit)
+ { insert(workspaceEditKey, workspaceEdit); }
+ void clearWorkspaceEdit() { remove(workspaceEditKey); }
+
+ // Capabilities specific to the `workspace/didChangeConfiguration` notification.
+ std::optional<DynamicRegistrationCapabilities> didChangeConfiguration() const
+ { return optionalValue<DynamicRegistrationCapabilities>(didChangeConfigurationKey); }
+ void setDidChangeConfiguration(const DynamicRegistrationCapabilities &didChangeConfiguration)
+ { insert(didChangeConfigurationKey, didChangeConfiguration); }
+ void clearDidChangeConfiguration() { remove(didChangeConfigurationKey); }
+
+ // Capabilities specific to the `workspace/didChangeWatchedFiles` notification.
+ std::optional<DynamicRegistrationCapabilities> didChangeWatchedFiles() const
+ { return optionalValue<DynamicRegistrationCapabilities>(didChangeWatchedFilesKey); }
+ void setDidChangeWatchedFiles(const DynamicRegistrationCapabilities &didChangeWatchedFiles)
+ { insert(didChangeWatchedFilesKey, didChangeWatchedFiles); }
+ void clearDidChangeWatchedFiles() { remove(didChangeWatchedFilesKey); }
+
+ // Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
+ std::optional<SymbolCapabilities> symbol() const
+ { return optionalValue<SymbolCapabilities>(symbolKey); }
+ void setSymbol(const SymbolCapabilities &symbol) { insert(symbolKey, symbol); }
+ void clearSymbol() { remove(symbolKey); }
+
+ // Capabilities specific to the `workspace/executeCommand` request.
+ std::optional<DynamicRegistrationCapabilities> executeCommand() const
+ { return optionalValue<DynamicRegistrationCapabilities>(executeCommandKey); }
+ void setExecuteCommand(const DynamicRegistrationCapabilities &executeCommand)
+ { insert(executeCommandKey, executeCommand); }
+ void clearExecuteCommand() { remove(executeCommandKey); }
+
+ // The client has support for workspace folders. Since 3.6.0
+ std::optional<bool> workspaceFolders() const
+ { return optionalValue<bool>(workspaceFoldersKey); }
+ void setWorkspaceFolders(bool workspaceFolders)
+ { insert(workspaceFoldersKey, workspaceFolders); }
+ void clearWorkspaceFolders() { remove(workspaceFoldersKey); }
+
+ // The client supports `workspace/configuration` requests. Since 3.6.0
+ std::optional<bool> configuration() const { return optionalValue<bool>(configurationKey); }
+ void setConfiguration(bool configuration) { insert(configurationKey, configuration); }
+ void clearConfiguration() { remove(configurationKey); }
+
+ std::optional<SemanticTokensWorkspaceClientCapabilities> semanticTokens() const
+ { return optionalValue<SemanticTokensWorkspaceClientCapabilities>(semanticTokensKey); }
+ void setSemanticTokens(const SemanticTokensWorkspaceClientCapabilities &semanticTokens)
+ { insert(semanticTokensKey, semanticTokens); }
+ void clearSemanticTokens() { remove(semanticTokensKey); }
+};
+
+class WindowClientClientCapabilities : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /**
+ * Whether client supports handling progress notifications.
+ * If set, servers are allowed to report in `workDoneProgress` property
+ * in the request specific server capabilities.
+ *
+ */
+ std::optional<bool> workDoneProgress() const
+ { return optionalValue<bool>(workDoneProgressKey); }
+ void setWorkDoneProgress(bool workDoneProgress)
+ { insert(workDoneProgressKey, workDoneProgress); }
+ void clearWorkDoneProgress() { remove(workDoneProgressKey); }
+
+private:
+ constexpr static const char workDoneProgressKey[] = "workDoneProgress";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ClientCapabilities : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // Workspace specific client capabilities.
+ std::optional<WorkspaceClientCapabilities> workspace() const
+ { return optionalValue<WorkspaceClientCapabilities>(workspaceKey); }
+ void setWorkspace(const WorkspaceClientCapabilities &workspace)
+ { insert(workspaceKey, workspace); }
+ void clearWorkspace() { remove(workspaceKey); }
+
+ // Text document specific client capabilities.
+ std::optional<TextDocumentClientCapabilities> textDocument() const
+ { return optionalValue<TextDocumentClientCapabilities>(textDocumentKey); }
+ void setTextDocument(const TextDocumentClientCapabilities &textDocument)
+ { insert(textDocumentKey, textDocument); }
+ void clearTextDocument() { remove(textDocumentKey); }
+
+ // Window specific client capabilities.
+ std::optional<WindowClientClientCapabilities> window() const
+ { return optionalValue<WindowClientClientCapabilities>(windowKey); }
+ void setWindow(const WindowClientClientCapabilities &window)
+ { insert(windowKey, window); }
+ void clearWindow() { remove(windowKey); }
+
+ // Experimental client capabilities.
+ QJsonValue experimental() const { return value(experimentalKey); }
+ void setExperimental(const QJsonValue &experimental) { insert(experimentalKey, experimental); }
+ void clearExperimental() { remove(experimentalKey); }
+};
+
+} // namespace lsp
diff --git a/src/shared/lsp/completion.cpp b/src/shared/lsp/completion.cpp
new file mode 100644
index 000000000..48a2ac868
--- /dev/null
+++ b/src/shared/lsp/completion.cpp
@@ -0,0 +1,59 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "completion.h"
+
+namespace lsp {
+
+constexpr const char CompletionRequest::methodName[];
+constexpr const char CompletionItemResolveRequest::methodName[];
+
+CompletionRequest::CompletionRequest(const CompletionParams &params)
+ : Request(methodName, params)
+{ }
+
+std::optional<MarkupOrString> CompletionItem::documentation() const
+{
+ QJsonValue documentation = value(documentationKey);
+ if (documentation.isUndefined())
+ return std::nullopt;
+ return MarkupOrString(documentation);
+}
+
+std::optional<CompletionItem::InsertTextFormat> CompletionItem::insertTextFormat() const
+{
+ if (std::optional<int> value = optionalValue<int>(insertTextFormatKey))
+ return std::make_optional(CompletionItem::InsertTextFormat(*value));
+ return std::nullopt;
+}
+
+std::optional<QList<CompletionItem::CompletionItemTag>> CompletionItem::tags() const
+{
+ if (const auto value = optionalValue<QJsonArray>(tagsKey)) {
+ QList<CompletionItemTag> tags;
+ for (auto it = value->cbegin(); it != value->cend(); ++it)
+ tags << static_cast<CompletionItemTag>(it->toInt());
+ return tags;
+ }
+ return {};
+}
+
+CompletionItemResolveRequest::CompletionItemResolveRequest(const CompletionItem &params)
+ : Request(methodName, params)
+{ }
+
+CompletionResult::CompletionResult(const QJsonValue &value)
+{
+ if (value.isNull()) {
+ emplace<std::nullptr_t>(nullptr);
+ } else if (value.isArray()) {
+ QList<CompletionItem> items;
+ for (auto arrayElement : value.toArray())
+ items << CompletionItem(arrayElement);
+ emplace<QList<CompletionItem>>(items);
+ } else if (value.isObject()) {
+ emplace<CompletionList>(CompletionList(value));
+ }
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/completion.h b/src/shared/lsp/completion.h
new file mode 100644
index 000000000..665b5a5ae
--- /dev/null
+++ b/src/shared/lsp/completion.h
@@ -0,0 +1,269 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+#include "languagefeatures.h"
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT CompletionParams : public TextDocumentPositionParams
+{
+public:
+ using TextDocumentPositionParams::TextDocumentPositionParams;
+
+ enum CompletionTriggerKind {
+ /**
+ * Completion was triggered by typing an identifier (24x7 code
+ * complete), manual invocation (e.g Ctrl+Space) or via API.
+ */
+ Invoked = 1,
+ /**
+ * Completion was triggered by a trigger character specified by
+ * the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
+ */
+ TriggerCharacter = 2,
+ /// Completion was re-triggered as the current completion list is incomplete.
+ TriggerForIncompleteCompletions = 3
+ };
+
+ class CompletionContext : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ /// How the completion was triggered.
+ CompletionTriggerKind triggerKind() const
+ { return CompletionTriggerKind(typedValue<int>(triggerKindKey)); }
+ void setTriggerKind(CompletionTriggerKind triggerKind)
+ { insert(triggerKindKey, triggerKind); }
+
+ /**
+ * The trigger character (a single character) that has trigger code complete.
+ * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
+ */
+ std::optional<QString> triggerCharacter() const
+ { return optionalValue<QString>(triggerCharacterKey); }
+ void setTriggerCharacter(const QString &triggerCharacter)
+ { insert(triggerCharacterKey, triggerCharacter); }
+ void clearTriggerCharacter() { remove(triggerCharacterKey); }
+
+ bool isValid() const override { return contains(triggerKindKey); }
+ };
+
+ /**
+ * The completion context. This is only available it the client specifies
+ * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true`
+ */
+ std::optional<CompletionContext> context() const
+ { return optionalValue<CompletionContext>(contextKey); }
+ void setContext(const CompletionContext &context)
+ { insert(contextKey, context); }
+ void clearContext() { remove(contextKey); }
+
+ // clangd extension
+ void setLimit(int limit) { insert(limitKey, limit); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CompletionItem : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /**
+ * The label of this completion item. By default also the text that is inserted when selecting
+ * this completion.
+ */
+ QString label() const { return typedValue<QString>(labelKey); }
+ void setLabel(const QString &label) { insert(labelKey, label); }
+
+ /// The kind of this completion item. Based of the kind an icon is chosen by the editor.
+ std::optional<int> kind() const { return optionalValue<int>(kindKey); }
+ void setKind(int kind) { insert(kindKey, kind); }
+ void clearKind() { remove(kindKey); }
+
+ /// A human-readable string with additional information about this item, like type information.
+ std::optional<QString> detail() const { return optionalValue<QString>(detailKey); }
+ void setDetail(const QString &detail) { insert(detailKey, detail); }
+ void clearDetail() { remove(detailKey); }
+
+ /// A human-readable string that represents a doc-comment.
+ std::optional<MarkupOrString> documentation() const;
+ void setDocumentation(const MarkupOrString &documentation)
+ { insert(documentationKey, documentation.toJson()); }
+ void cleatDocumentation() { remove(documentationKey); }
+
+ /// A string that should be used when comparing this item
+ /// with other items. When `falsy` the label is used.
+ std::optional<QString> sortText() const { return optionalValue<QString>(sortTextKey); }
+ void setSortText(const QString &sortText) { insert(sortTextKey, sortText); }
+ void clearSortText() { remove(sortTextKey); }
+
+ /// A string that should be used when filtering a set of
+ /// completion items. When `falsy` the label is used.
+ std::optional<QString> filterText() const { return optionalValue<QString>(filterTextKey); }
+ void setFilterText(const QString &filterText) { insert(filterTextKey, filterText); }
+ void clearFilterText() { remove(filterTextKey); }
+
+ /**
+ * A string that should be inserted into a document when selecting
+ * this completion. When `falsy` the label is used.
+ *
+ * The `insertText` is subject to interpretation by the client side.
+ * Some tools might not take the string literally. For example
+ * VS Code when code complete is requested in this example `con<cursor position>`
+ * and a completion item with an `insertText` of `console` is provided it
+ * will only insert `sole`. Therefore it is recommended to use `textEdit` instead
+ * since it avoids additional client side interpretation.
+ *
+ * @deprecated Use textEdit instead.
+ */
+ std::optional<QString> insertText() const { return optionalValue<QString>(insertTextKey); }
+ void setInsertText(const QString &insertText) { insert(insertTextKey, insertText); }
+ void clearInsertText() { remove(insertTextKey); }
+
+ enum InsertTextFormat {
+ /// The primary text to be inserted is treated as a plain string.
+ PlainText = 1,
+
+ /**
+ * The primary text to be inserted is treated as a snippet.
+ *
+ * A snippet can define tab stops and placeholders with `$1`, `$2`
+ * and `${3:foo}`. `$0` defines the final tab stop, it defaults to
+ * the end of the snippet. Placeholders with equal identifiers are linked,
+ * that is typing in one will update others too.
+ */
+ Snippet = 2
+ };
+
+ /// The format of the insert text. The format applies to both the `insertText` property
+ /// and the `newText` property of a provided `textEdit`.
+ std::optional<InsertTextFormat> insertTextFormat() const;
+ void setInsertTextFormat(const InsertTextFormat &insertTextFormat)
+ { insert(insertTextFormatKey, insertTextFormat); }
+ void clearInsertTextFormat() { remove(insertTextFormatKey); }
+
+ /**
+ * An edit which is applied to a document when selecting this completion. When an edit is provided the value of
+ * `insertText` is ignored.
+ *
+ * *Note:* The range of the edit must be a single line range and it must contain the position at which completion
+ * has been requested.
+ */
+ std::optional<TextEdit> textEdit() const { return optionalValue<TextEdit>(textEditKey); }
+ void setTextEdit(const TextEdit &textEdit) { insert(textEditKey, textEdit); }
+ void clearTextEdit() { remove(textEditKey); }
+
+ /**
+ * An optional array of additional text edits that are applied when
+ * selecting this completion. Edits must not overlap with the main edit
+ * nor with themselves.
+ */
+ std::optional<QList<TextEdit>> additionalTextEdits() const
+ { return optionalArray<TextEdit>(additionalTextEditsKey); }
+ void setAdditionalTextEdits(const QList<TextEdit> &additionalTextEdits)
+ { insertArray(additionalTextEditsKey, additionalTextEdits); }
+ void clearAdditionalTextEdits() { remove(additionalTextEditsKey); }
+
+ /**
+ * An optional set of characters that when pressed while this completion is active will accept it first and
+ * then type that character. *Note* that all commit characters should have `length=1` and that superfluous
+ * characters will be ignored.
+ */
+ std::optional<QList<QString>> commitCharacters() const
+ { return optionalArray<QString>(commitCharactersKey); }
+ void setCommitCharacters(const QList<QString> &commitCharacters)
+ { insertArray(commitCharactersKey, commitCharacters); }
+ void clearCommitCharacters() { remove(commitCharactersKey); }
+
+ /**
+ * An optional command that is executed *after* inserting this completion. *Note* that
+ * additional modifications to the current document should be described with the
+ * additionalTextEdits-property.
+ */
+ std::optional<Command> command() const { return optionalValue<Command>(commandKey); }
+ void setCommand(const Command &command) { insert(commandKey, command); }
+ void clearCommand() { remove(commandKey); }
+
+ /**
+ * An data entry field that is preserved on a completion item between
+ * a completion and a completion resolve request.
+ */
+ std::optional<QJsonValue> data() const { return optionalValue<QJsonValue>(dataKey); }
+ void setData(const QJsonValue &data) { insert(dataKey, data); }
+ void clearData() { remove(dataKey); }
+
+ /**
+ * Completion item tags are extra annotations that tweak the rendering of a
+ * completion item.
+ * @since 3.15.0
+ */
+ enum CompletionItemTag {
+ Deprecated = 1,
+ };
+
+ /**
+ * Tags for this completion item.
+ * @since 3.15.0
+ */
+ std::optional<QList<CompletionItemTag>> tags() const;
+
+ /**
+ * Indicates if this item is deprecated.
+ * @deprecated Use `tags` instead if supported.
+ */
+ std::optional<bool> deprecated() const { return optionalValue<bool>(deprecatedKey); }
+
+ bool isValid() const override { return contains(labelKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CompletionList : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /**
+ * This list it not complete. Further typing should result in recomputing
+ * this list.
+ */
+ bool isIncomplete() const { return typedValue<bool>(isIncompleteKey); }
+ void setIsIncomplete(bool isIncomplete) { insert(isIncompleteKey, isIncomplete); }
+
+ /// The completion items.
+ std::optional<QList<CompletionItem>> items() const { return array<CompletionItem>(itemsKey); }
+ void setItems(const QList<CompletionItem> &items) { insertArray(itemsKey, items); }
+ void clearItems() { remove(itemsKey); }
+
+ bool isValid() const override { return contains(isIncompleteKey); }
+};
+
+/// The result of a completion is CompletionItem[] | CompletionList | null
+class LANGUAGESERVERPROTOCOL_EXPORT CompletionResult
+ : public std::variant<QList<CompletionItem>, CompletionList, std::nullptr_t>
+{
+public:
+ using variant::variant;
+ explicit CompletionResult(const QJsonValue &value);
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CompletionRequest : public Request<
+ CompletionResult, std::nullptr_t, CompletionParams>
+{
+public:
+ explicit CompletionRequest(const CompletionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/completion";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CompletionItemResolveRequest : public Request<
+ CompletionItem, std::nullptr_t, CompletionItem>
+{
+public:
+ explicit CompletionItemResolveRequest(const CompletionItem &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "completionItem/resolve";
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/diagnostics.cpp b/src/shared/lsp/diagnostics.cpp
new file mode 100644
index 000000000..352b1572f
--- /dev/null
+++ b/src/shared/lsp/diagnostics.cpp
@@ -0,0 +1,13 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "diagnostics.h"
+
+namespace lsp {
+
+constexpr const char PublishDiagnosticsNotification::methodName[];
+
+PublishDiagnosticsNotification::PublishDiagnosticsNotification(const PublishDiagnosticsParams &params)
+ : Notification(methodName, params) { }
+
+} // namespace lsp
diff --git a/src/shared/lsp/diagnostics.h b/src/shared/lsp/diagnostics.h
new file mode 100644
index 000000000..ae479a23b
--- /dev/null
+++ b/src/shared/lsp/diagnostics.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+#include "languagefeatures.h"
+
+namespace lsp {
+
+class PublishDiagnosticsParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ QList<Diagnostic> diagnostics() const { return array<Diagnostic>(diagnosticsKey); }
+ void setDiagnostics(const QList<Diagnostic> &diagnostics)
+ { insertArray(diagnosticsKey, diagnostics); }
+
+ std::optional<int> version() const { return optionalValue<int>(versionKey); }
+ void setVersion(int version) { insert(versionKey, version); }
+ void clearVersion() { remove(versionKey); }
+
+ bool isValid() const override { return contains(diagnosticsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT PublishDiagnosticsNotification : public Notification<PublishDiagnosticsParams>
+{
+public:
+ explicit PublishDiagnosticsNotification(const PublishDiagnosticsParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "textDocument/publishDiagnostics";
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/filepath.h b/src/shared/lsp/filepath.h
new file mode 100644
index 000000000..736085828
--- /dev/null
+++ b/src/shared/lsp/filepath.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** 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/hostosinfo.h>
+
+#include <QDir>
+#include <QFileInfo>
+#include <QString>
+
+namespace lsp::Utils {
+
+class FilePath : public QString
+{
+public:
+ FilePath() = default;
+ FilePath(const QString &s) : QString(s) {}
+
+ QString scheme() const { return {}; }
+ QString host() const { return {}; }
+ Qt::CaseSensitivity caseSensitivity() const {
+ return qbs::Internal::HostOsInfo::fileNameCaseSensitivity();
+ }
+ QString toString() const { return *this; }
+ QString path() const { return toString(); }
+
+ static FilePath fromUserInput(const QString &s) { return QDir::fromNativeSeparators(s); }
+ QString toUserOutput() const { return QDir::toNativeSeparators(*this); }
+};
+
+} // namespace lsp::Utils
diff --git a/src/shared/lsp/initializemessages.cpp b/src/shared/lsp/initializemessages.cpp
new file mode 100644
index 000000000..db0abcf49
--- /dev/null
+++ b/src/shared/lsp/initializemessages.cpp
@@ -0,0 +1,137 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "initializemessages.h"
+
+#include <QCoreApplication>
+
+namespace lsp {
+
+constexpr const char InitializeRequest::methodName[];
+constexpr const char InitializeNotification::methodName[];
+constexpr Trace::Values s_trace = Trace::off;
+
+Trace Trace::fromString(const QString &val)
+{
+ if (val == "messages")
+ return messages;
+ if (val == "verbose")
+ return verbose;
+ return off;
+}
+
+#define RETURN_CASE(name) case Trace::name: return QString(#name);
+QString Trace::toString() const
+{
+ switch (m_value) {
+ RETURN_CASE(off);
+ RETURN_CASE(messages);
+ RETURN_CASE(verbose);
+ }
+ return QString("off");
+}
+#undef RETURN_CASE
+
+std::optional<QList<MarkupKind>>
+TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities::
+documentationFormat() const
+{
+ return optionalArray<MarkupKind>(documentationFormatKey);
+}
+
+void
+TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities::
+setDocumentationFormat(const QList<MarkupKind> &documentationFormat)
+{
+ insertArray(documentationFormatKey, documentationFormat);
+}
+
+TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities::CompletionItemKindCapabilities()
+{
+ setValueSet({CompletionItemKind::Text, CompletionItemKind::Method, CompletionItemKind::Function,
+ CompletionItemKind::Constructor, CompletionItemKind::Field, CompletionItemKind::Variable,
+ CompletionItemKind::Class, CompletionItemKind::Interface, CompletionItemKind::Module,
+ CompletionItemKind::Property, CompletionItemKind::Unit, CompletionItemKind::Value,
+ CompletionItemKind::Enum, CompletionItemKind::Keyword, CompletionItemKind::Snippet,
+ CompletionItemKind::Color, CompletionItemKind::File, CompletionItemKind::Reference,
+ CompletionItemKind::Folder, CompletionItemKind::EnumMember, CompletionItemKind::Constant,
+ CompletionItemKind::Struct, CompletionItemKind::Event, CompletionItemKind::Operator,
+ CompletionItemKind::TypeParameter});
+}
+
+std::optional<QList<CompletionItemKind::Kind>>
+TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities::
+valueSet() const
+{
+ if (std::optional<QList<int>> array = optionalArray<int>(valueSetKey)) {
+ return std::make_optional(Utils::transform(*array, [](int value) {
+ return static_cast<CompletionItemKind::Kind>(value);
+ }));
+ }
+ return std::nullopt;
+}
+
+void
+TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities::
+setValueSet(const QList<CompletionItemKind::Kind> &valueSet)
+{
+ insert(valueSetKey, enumArrayToJsonArray<CompletionItemKind::Kind>(valueSet));
+}
+
+std::optional<QList<MarkupKind> > TextDocumentClientCapabilities::HoverCapabilities::contentFormat() const
+{
+ return optionalArray<MarkupKind>(contentFormatKey);
+}
+
+void TextDocumentClientCapabilities::HoverCapabilities::setContentFormat(const QList<MarkupKind> &contentFormat)
+{
+ insertArray(contentFormatKey, contentFormat);
+}
+
+std::optional<QList<MarkupKind>>
+TextDocumentClientCapabilities::SignatureHelpCapabilities::SignatureInformationCapabilities::
+documentationFormat() const
+{
+ return optionalArray<MarkupKind>(documentationFormatKey);
+}
+
+void
+TextDocumentClientCapabilities::SignatureHelpCapabilities::SignatureInformationCapabilities::
+setDocumentationFormat(const QList<MarkupKind> &documentationFormat)
+{
+ insertArray(documentationFormatKey, documentationFormat);
+}
+
+InitializeParams::InitializeParams()
+{
+ setProcessId(int(QCoreApplication::applicationPid()));
+ setRootUri(LanguageClientValue<DocumentUri>());
+ setCapabilities(ClientCapabilities());
+ setTrace(s_trace);
+}
+
+std::optional<QJsonObject> InitializeParams::initializationOptions() const
+{
+ const QJsonValue &optionsValue = value(initializationOptionsKey);
+ if (optionsValue.isObject())
+ return optionsValue.toObject();
+ return std::nullopt;
+}
+
+std::optional<Trace> InitializeParams::trace() const
+{
+ const QJsonValue &traceValue = value(traceKey);
+ if (traceValue.isUndefined())
+ return std::nullopt;
+ return std::make_optional(Trace(traceValue.toString()));
+}
+
+InitializeRequest::InitializeRequest(const InitializeParams &params)
+ : Request(methodName, params)
+{ }
+
+InitializeNotification::InitializeNotification(const InitializedParams &params)
+ : Notification(methodName, params)
+{ }
+
+} // namespace lsp
diff --git a/src/shared/lsp/initializemessages.h b/src/shared/lsp/initializemessages.h
new file mode 100644
index 000000000..159d62ded
--- /dev/null
+++ b/src/shared/lsp/initializemessages.h
@@ -0,0 +1,187 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "clientcapabilities.h"
+#include "jsonrpcmessages.h"
+#include "lsptypes.h"
+#include "servercapabilities.h"
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT Trace
+{
+public:
+ enum Values
+ {
+ off,
+ messages,
+ verbose
+ };
+
+ Trace() = default;
+ Trace(Values val) : m_value(val) {}
+ Trace(QString val) : Trace(fromString(val)) {}
+
+ static Trace fromString(const QString& val);
+ QString toString() const;
+
+private:
+ Values m_value = off;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ClientInfo : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString name() const { return typedValue<QString>(nameKey); }
+ void setName(const QString &name) { insert(nameKey, name); }
+
+ std::optional<QString> version() const { return optionalValue<QString>(versionKey); }
+ void setVersion(const QString &version) { insert(versionKey, version); }
+ void clearVersion() { remove(versionKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT InitializeParams : public JsonObject
+{
+public:
+ InitializeParams();
+ using JsonObject::JsonObject;
+
+ /*
+ * The process Id of the parent process that started
+ * the server. Is null if the process has not been started by another process.
+ * If the parent process is not alive then the server should exit (see exit notification)
+ * its process.
+ */
+ LanguageClientValue<int> processId() const { return clientValue<int>(processIdKey); }
+ void setProcessId(const LanguageClientValue<int> &id) { insert(processIdKey, id); }
+
+ /*
+ * The rootPath of the workspace. Is null
+ * if no folder is open.
+ *
+ * @deprecated in favor of rootUri.
+ */
+ std::optional<LanguageClientValue<QString>> rootPath() const
+ { return optionalClientValue<QString>(rootPathKey); }
+ void setRootPath(const LanguageClientValue<QString> &path)
+ { insert(rootPathKey, path); }
+ void clearRootPath() { remove(rootPathKey); }
+
+ /*
+ * The rootUri of the workspace. Is null if no
+ * folder is open. If both `rootPath` and `rootUri` are set
+ * `rootUri` wins.
+ */
+ LanguageClientValue<DocumentUri> rootUri() const
+ { return clientValue<QString>(rootUriKey).transform<DocumentUri>(); }
+ void setRootUri(const LanguageClientValue<DocumentUri> &uri)
+ { insert(rootUriKey, uri); }
+
+ // User provided initialization options.
+ std::optional<QJsonObject> initializationOptions() const;
+ void setInitializationOptions(const QJsonObject &options)
+ { insert(initializationOptionsKey, options); }
+ void clearInitializationOptions() { remove(initializationOptionsKey); }
+
+ // The capabilities provided by the client (editor or tool)
+ ClientCapabilities capabilities() const { return typedValue<ClientCapabilities>(capabilitiesKey); }
+ void setCapabilities(const ClientCapabilities &capabilities)
+ { insert(capabilitiesKey, capabilities); }
+
+ // The initial trace setting. If omitted trace is disabled ('off').
+ std::optional<Trace> trace() const;
+ void setTrace(Trace trace) { insert(traceKey, trace.toString()); }
+ void clearTrace() { remove(traceKey); }
+
+ /*
+ * The workspace folders configured in the client when the server starts.
+ * This property is only available if the client supports workspace folders.
+ * It can be `null` if the client supports workspace folders but none are
+ * configured.
+ *
+ * Since 3.6.0
+ */
+ std::optional<LanguageClientArray<WorkSpaceFolder>> workspaceFolders() const
+ { return optionalClientArray<WorkSpaceFolder>(workspaceFoldersKey); }
+ void setWorkSpaceFolders(const LanguageClientArray<WorkSpaceFolder> &folders)
+ { insert(workspaceFoldersKey, folders.toJson()); }
+ void clearWorkSpaceFolders() { remove(workspaceFoldersKey); }
+
+ std::optional<ClientInfo> clientInfo() const { return optionalValue<ClientInfo>(clientInfoKey); }
+ void setClientInfo(const ClientInfo &clientInfo) { insert(clientInfoKey, clientInfo); }
+ void clearClientInfo() { remove(clientInfoKey); }
+
+ bool isValid() const override
+ { return contains(processIdKey) && contains(rootUriKey) && contains(capabilitiesKey); }
+};
+
+using InitializedParams = JsonObject;
+
+class LANGUAGESERVERPROTOCOL_EXPORT InitializeNotification : public Notification<InitializedParams>
+{
+public:
+ explicit InitializeNotification(const InitializedParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "initialized";
+
+ bool parametersAreValid(QString * /*errorMessage*/) const final { return true; }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ServerInfo : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString name() const { return typedValue<QString>(nameKey); }
+ std::optional<QString> version() const { return optionalValue<QString>(versionKey); }
+
+ bool isValid() const override { return contains(nameKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT InitializeResult : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ ServerCapabilities capabilities() const
+ { return typedValue<ServerCapabilities>(capabilitiesKey); }
+ void setCapabilities(const ServerCapabilities &capabilities)
+ { insert(capabilitiesKey, capabilities); }
+
+ std::optional<ServerInfo> serverInfo() const
+ { return optionalValue<ServerInfo>(serverInfoKey); }
+
+ bool isValid() const override { return contains(capabilitiesKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT InitializeError : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /*
+ * Indicates whether the client execute the following retry logic:
+ * (1) show the message provided by the ResponseError to the user
+ * (2) user selects retry or cancel
+ * (3) if user selected retry the initialize method is sent again.
+ */
+ bool retry() const { return typedValue<bool>(retryKey); }
+ void setRetry(const bool &retry) { insert(retryKey, retry); }
+
+ bool isValid() const override { return contains(retryKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT InitializeRequest : public Request<
+ InitializeResult, InitializeError, InitializeParams>
+{
+public:
+ explicit InitializeRequest(const InitializeParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "initialize";
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/jsonkeys.h b/src/shared/lsp/jsonkeys.h
new file mode 100644
index 000000000..5e5491caa
--- /dev/null
+++ b/src/shared/lsp/jsonkeys.h
@@ -0,0 +1,231 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace lsp {
+
+constexpr char actionsKey[] = "actions";
+constexpr char activeParameterKey[] = "activeParameter";
+constexpr char activeParameterSupportKey[] = "activeParameterSupport";
+constexpr char activeSignatureKey[] = "activeSignature";
+constexpr char addedKey[] = "added";
+constexpr char additionalTextEditsKey[] = "additionalTextEdits";
+constexpr char alphaKey[] = "alpha";
+constexpr char appliedKey[] = "applied";
+constexpr char applyEditKey[] = "applyEdit";
+constexpr char argumentsKey[] = "arguments";
+constexpr char blueKey[] = "blue";
+constexpr char callHierarchyKey[] = "callHierarchy";
+constexpr char callHierarchyProviderKey[] = "callHierarchyProvider";
+constexpr char cancellableKey[] = "cancellable";
+constexpr char capabilitiesKey[] = "capabilities";
+constexpr char chKey[] = "ch";
+constexpr char changeKey[] = "change";
+constexpr char changeNotificationsKey[] = "changeNotifications";
+constexpr char changesKey[] = "changes";
+constexpr char characterKey[] = "character";
+constexpr char childrenKey[] = "children";
+constexpr char clientInfoKey[] = "clientInfo";
+constexpr char codeActionKey[] = "codeAction";
+constexpr char codeActionKindKey[] = "codeActionKind";
+constexpr char codeActionKindsKey[] = "codeActionKinds";
+constexpr char codeActionLiteralSupportKey[] = "codeActionLiteralSupport";
+constexpr char codeActionProviderKey[] = "codeActionProvider";
+constexpr char codeKey[] = "code";
+constexpr char codeLensKey[] = "codeLens";
+constexpr char codeLensProviderKey[] = "codeLensProvider";
+constexpr char colorInfoKey[] = "colorInfo";
+constexpr char colorKey[] = "color";
+constexpr char colorProviderKey[] = "colorProvider";
+constexpr char commandKey[] = "command";
+constexpr char commandsKey[] = "commands";
+constexpr char commitCharacterSupportKey[] = "commitCharacterSupport";
+constexpr char commitCharactersKey[] = "commitCharacters";
+constexpr char completionItemKey[] = "completionItem";
+constexpr char completionItemKindKey[] = "completionItemKind";
+constexpr char completionKey[] = "completion";
+constexpr char completionProviderKey[] = "completionProvider";
+constexpr char configurationKey[] = "configuration";
+constexpr char containerNameKey[] = "containerName";
+constexpr char contentChangesKey[] = "contentChanges";
+constexpr char contentFormatKey[] = "contentFormat";
+constexpr char contentKey[] = "value";
+constexpr char contentsKey[] = "contents";
+constexpr char contextKey[] = "context";
+constexpr char contextSupportKey[] = "contextSupport";
+constexpr char dataKey[] = "data";
+constexpr char definitionKey[] = "definition";
+constexpr char definitionProviderKey[] = "definitionProvider";
+constexpr char deleteCountKey[] = "deleteCount";
+constexpr char deltaKey[] = "delta";
+constexpr char deprecatedKey[] = "deprecated";
+constexpr char detailKey[] = "detail";
+constexpr char diagnosticsKey[] = "diagnostics";
+constexpr char didChangeConfigurationKey[] = "didChangeConfiguration";
+constexpr char didChangeWatchedFilesKey[] = "didChangeWatchedFiles";
+constexpr char didSaveKey[] = "didSave";
+constexpr char documentChangesKey[] = "documentChanges";
+constexpr char documentFormattingProviderKey[] = "documentFormattingProvider";
+constexpr char documentHighlightKey[] = "documentHighlight";
+constexpr char documentHighlightProviderKey[] = "documentHighlightProvider";
+constexpr char documentLinkKey[] = "documentLink";
+constexpr char documentLinkProviderKey[] = "documentLinkProvider";
+constexpr char documentRangeFormattingProviderKey[] = "documentRangeFormattingProvider";
+constexpr char documentSelectorKey[] = "documentSelector";
+constexpr char documentSymbolKey[] = "documentSymbol";
+constexpr char documentSymbolProviderKey[] = "documentSymbolProvider";
+constexpr char documentationFormatKey[] = "documentationFormat";
+constexpr char documentationKey[] = "documentation";
+constexpr char dynamicRegistrationKey[] = "dynamicRegistration";
+constexpr char editKey[] = "edit";
+constexpr char editsKey[] = "edits";
+constexpr char endKey[] = "end";
+constexpr char errorKey[] = "error";
+constexpr char eventKey[] = "event";
+constexpr char executeCommandKey[] = "executeCommand";
+constexpr char executeCommandProviderKey[] = "executeCommandProvider";
+constexpr char experimentalKey[] = "experimental";
+constexpr char filterTextKey[] = "filterText";
+constexpr char firstTriggerCharacterKey[] = "firstTriggerCharacter";
+constexpr char formatsKey[] = "formats";
+constexpr char formattingKey[] = "formatting";
+constexpr char fromKey[] = "from";
+constexpr char fromRangesKey[] = "fromRanges";
+constexpr char fullKey[] = "full";
+constexpr char greenKey[] = "green";
+constexpr char hierarchicalDocumentSymbolSupportKey[] = "hierarchicalDocumentSymbolSupport";
+constexpr char hoverKey[] = "hover";
+constexpr char hoverProviderKey[] = "hoverProvider";
+constexpr char idKey[] = "id";
+constexpr char ignoreIfExistsKey[] = "ignoreIfExists";
+constexpr char ignoreIfNotExistsKey[] = "ignoreIfNotExists";
+constexpr char implementationKey[] = "implementation";
+constexpr char implementationProviderKey[] = "implementationProvider";
+constexpr char includeDeclarationKey[] = "includeDeclaration";
+constexpr char includeTextKey[] = "includeText";
+constexpr char initializationOptionsKey[] = "initializationOptions";
+constexpr char insertFinalNewlineKey[] = "insertFinalNewline";
+constexpr char insertSpaceKey[] = "insertSpace";
+constexpr char insertTextFormatKey[] = "insertTextFormat";
+constexpr char insertTextKey[] = "insertText";
+constexpr char isIncompleteKey[] = "isIncomplete";
+constexpr char itemKey[] = "item";
+constexpr char itemsKey[] = "items";
+constexpr char jsonRpcVersionKey[] = "jsonrpc";
+constexpr char kindKey[] = "kind";
+constexpr char labelKey[] = "label";
+constexpr char languageIdKey[] = "languageId";
+constexpr char languageKey[] = "language";
+constexpr char legendKey[] = "legend";
+constexpr char limitKey[] = "limit";
+constexpr char lineKey[] = "line";
+constexpr char linesKey[] = "lines";
+constexpr char locationKey[] = "location";
+constexpr char messageKey[] = "message";
+constexpr char methodKey[] = "method";
+constexpr char moreTriggerCharacterKey[] = "moreTriggerCharacter";
+constexpr char multiLineTokenSupportKey[] = "multiLineTokenSupport";
+constexpr char nameKey[] = "name";
+constexpr char newNameKey[] = "newName";
+constexpr char newTextKey[] = "newText";
+constexpr char newUriKey[] = "newUri";
+constexpr char oldUriKey[] = "oldUri";
+constexpr char onTypeFormattingKey[] = "onTypeFormatting";
+constexpr char onlyKey[] = "only";
+constexpr char openCloseKey[] = "openClose";
+constexpr char optionsKey[] = "options";
+constexpr char overlappingTokenSupportKey[] = "overlappingTokenSupport";
+constexpr char overwriteKey[] = "overwrite";
+constexpr char parametersKey[] = "parameters";
+constexpr char paramsKey[] = "params";
+constexpr char patternKey[] = "pattern";
+constexpr char percentageKey[] = "percentage";
+constexpr char placeHolderKey[] = "placeHolder";
+constexpr char positionKey[] = "position";
+constexpr char prepareProviderKey[] = "prepareProvider";
+constexpr char prepareSupportKey[] = "prepareSupport";
+constexpr char previousResultIdKey[] = "previousResultId";
+constexpr char processIdKey[] = "processId";
+constexpr char queryKey[] = "query";
+constexpr char rangeFormattingKey[] = "rangeFormatting";
+constexpr char rangeKey[] = "range";
+constexpr char rangeLengthKey[] = "rangeLength";
+constexpr char reasonKey[] = "reason";
+constexpr char recursiveKey[] = "recursive";
+constexpr char redKey[] = "red";
+constexpr char referencesKey[] = "references";
+constexpr char referencesProviderKey[] = "referencesProvider";
+constexpr char refreshSupportKey[] = "refreshSupport";
+constexpr char registerOptionsKey[] = "registerOptions";
+constexpr char registrationsKey[] = "registrations";
+constexpr char removedKey[] = "removed";
+constexpr char renameKey[] = "rename";
+constexpr char renameProviderKey[] = "renameProvider";
+constexpr char requestsKey[] = "requests";
+constexpr char resolveProviderKey[] = "resolveProvider";
+constexpr char resourceOperationsKey[] = "resourceOperations";
+constexpr char resultIdKey[] = "resultId";
+constexpr char resultKey[] = "result";
+constexpr char retryKey[] = "retry";
+constexpr char rootPathKey[] = "rootPath";
+constexpr char rootUriKey[] = "rootUri";
+constexpr char saveKey[] = "save";
+constexpr char schemeKey[] = "scheme";
+constexpr char scopeUriKey[] = "scopeUri";
+constexpr char sectionKey[] = "section";
+constexpr char selectionRangeKey[] = "selectionRange";
+constexpr char semanticTokensKey[] = "semanticTokens";
+constexpr char semanticTokensProviderKey[] = "semanticTokensProvider";
+constexpr char serverInfoKey[] = "serverInfo";
+constexpr char settingsKey[] = "settings";
+constexpr char severityKey[] = "severity";
+constexpr char signatureHelpKey[] = "signatureHelp";
+constexpr char signatureHelpProviderKey[] = "signatureHelpProvider";
+constexpr char signatureInformationKey[] = "signatureInformation";
+constexpr char signaturesKey[] = "signatures";
+constexpr char snippetSupportKey[] = "snippetSupport";
+constexpr char sortTextKey[] = "sortText";
+constexpr char sourceKey[] = "source";
+constexpr char startKey[] = "start";
+constexpr char supportedKey[] = "supported";
+constexpr char symbolKey[] = "symbol";
+constexpr char symbolKindKey[] = "symbolKind";
+constexpr char syncKindKey[] = "syncKind";
+constexpr char synchronizationKey[] = "synchronization";
+constexpr char tabSizeKey[] = "tabSize";
+constexpr char tagsKey[] = "tags";
+constexpr char targetKey[] = "target";
+constexpr char textDocumentKey[] = "textDocument";
+constexpr char textDocumentSyncKey[] = "textDocumentSync";
+constexpr char textEditKey[] = "textEdit";
+constexpr char textKey[] = "text";
+constexpr char titleKey[] = "title";
+constexpr char toKey[] = "to";
+constexpr char tokenKey[] = "token";
+constexpr char tokenModifiersKey[] = "tokenModifiers";
+constexpr char tokenTypesKey[] = "tokenTypes";
+constexpr char traceKey[] = "trace";
+constexpr char triggerCharacterKey[] = "triggerCharacter";
+constexpr char triggerCharactersKey[] = "triggerCharacters";
+constexpr char triggerKindKey[] = "triggerKind";
+constexpr char trimFinalNewlinesKey[] = "trimFinalNewlines";
+constexpr char trimTrailingWhitespaceKey[] = "trimTrailingWhitespace";
+constexpr char typeDefinitionKey[] = "typeDefinition";
+constexpr char typeDefinitionProviderKey[] = "typeDefinitionProvider";
+constexpr char typeKey[] = "type";
+constexpr char unregistrationsKey[] = "unregistrations";
+constexpr char uriKey[] = "uri";
+constexpr char valueKey[] = "value";
+constexpr char valueSetKey[] = "valueSet";
+constexpr char versionKey[] = "version";
+constexpr char willSaveKey[] = "willSave";
+constexpr char willSaveWaitUntilKey[] = "willSaveWaitUntil";
+constexpr char windowKey[] = "window";
+constexpr char workDoneProgressKey[] = "workDoneProgress";
+constexpr char workspaceEditKey[] = "workspaceEdit";
+constexpr char workspaceFoldersKey[] = "workspaceFolders";
+constexpr char workspaceKey[] = "workspace";
+constexpr char workspaceSymbolProviderKey[] = "workspaceSymbolProvider";
+
+} // namespace lsp
diff --git a/src/shared/lsp/jsonobject.cpp b/src/shared/lsp/jsonobject.cpp
new file mode 100644
index 000000000..887d79a5e
--- /dev/null
+++ b/src/shared/lsp/jsonobject.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "jsonobject.h"
+
+#include <QCoreApplication>
+
+namespace lsp {
+
+JsonObject &JsonObject::operator=(const JsonObject &other) = default;
+
+JsonObject &JsonObject::operator=(JsonObject &&other)
+{
+ m_jsonObject.swap(other.m_jsonObject);
+ return *this;
+}
+
+QJsonObject::iterator JsonObject::insert(const std::string_view key, const JsonObject &object)
+{
+ return m_jsonObject.insert(QLatin1String(key.data()), object.m_jsonObject);
+}
+
+QJsonObject::iterator JsonObject::insert(const std::string_view key, const QJsonValue &value)
+{
+ return m_jsonObject.insert(QLatin1String(key.data()), value);
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/jsonobject.h b/src/shared/lsp/jsonobject.h
new file mode 100644
index 000000000..29811839f
--- /dev/null
+++ b/src/shared/lsp/jsonobject.h
@@ -0,0 +1,181 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "algorithm.h"
+#include "languageserverprotocol_global.h"
+#include "lsputils.h"
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QJsonObject>
+
+#include <optional>
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT JsonObject
+{
+public:
+ using iterator = QJsonObject::iterator;
+ using const_iterator = QJsonObject::const_iterator;
+
+ JsonObject() = default;
+
+ explicit JsonObject(const QJsonObject &object) : m_jsonObject(object) { }
+ explicit JsonObject(QJsonObject &&object) : m_jsonObject(std::move(object)) { }
+
+ JsonObject(const JsonObject &object) : m_jsonObject(object.m_jsonObject) { }
+ JsonObject(JsonObject &&object) : m_jsonObject(std::move(object.m_jsonObject)) { }
+
+ explicit JsonObject(const QJsonValue &value) : m_jsonObject(value.toObject()) { }
+
+ virtual ~JsonObject() = default;
+
+ JsonObject &operator=(const JsonObject &other);
+ JsonObject &operator=(JsonObject &&other);
+
+ bool operator==(const JsonObject &other) const { return m_jsonObject == other.m_jsonObject; }
+
+ operator const QJsonObject&() const { return m_jsonObject; }
+
+ virtual bool isValid() const { return true; }
+
+ iterator end() { return m_jsonObject.end(); }
+ const_iterator end() const { return m_jsonObject.end(); }
+
+ iterator insert(const std::string_view key, const JsonObject &value);
+ iterator insert(const std::string_view key, const QJsonValue &value);
+
+protected:
+
+ template <typename T, typename V>
+ iterator insertVariant(const std::string_view key, const V &variant);
+ template <typename T1, typename T2, typename... Args, typename V>
+ iterator insertVariant(const std::string_view key, const V &variant);
+
+ // QJSonObject redirections
+ QJsonValue value(const std::string_view key) const { return m_jsonObject.value(QLatin1String(key.data())); }
+ bool contains(const std::string_view key) const { return m_jsonObject.contains(QLatin1String(key.data())); }
+ iterator find(const std::string_view key) { return m_jsonObject.find(QLatin1String(key.data())); }
+ const_iterator find(const std::string_view key) const { return m_jsonObject.find(QLatin1String(key.data())); }
+ void remove(const std::string_view key) { m_jsonObject.remove(QLatin1String(key.data())); }
+ QStringList keys() const { return m_jsonObject.keys(); }
+
+ // convenience value access
+ template<typename T>
+ T typedValue(const std::string_view key) const;
+ template<typename T>
+ std::optional<T> optionalValue(const std::string_view key) const;
+ template<typename T>
+ LanguageClientValue<T> clientValue(const std::string_view key) const;
+ template<typename T>
+ std::optional<LanguageClientValue<T>> optionalClientValue(const std::string_view key) const;
+ template<typename T>
+ QList<T> array(const std::string_view key) const;
+ template<typename T>
+ std::optional<QList<T>> optionalArray(const std::string_view key) const;
+ template<typename T>
+ LanguageClientArray<T> clientArray(const std::string_view key) const;
+ template<typename T>
+ std::optional<LanguageClientArray<T>> optionalClientArray(const std::string_view key) const;
+ template<typename T>
+ void insertArray(const std::string_view key, const QList<T> &array);
+ template<typename>
+ void insertArray(const std::string_view key, const QList<JsonObject> &array);
+
+private:
+ QJsonObject m_jsonObject;
+};
+
+template<typename T, typename V>
+JsonObject::iterator JsonObject::insertVariant(const std::string_view key, const V &variant)
+{
+ return std::holds_alternative<T>(variant) ? insert(key, std::get<T>(variant)) : end();
+}
+
+template<typename T1, typename T2, typename... Args, typename V>
+JsonObject::iterator JsonObject::insertVariant(const std::string_view key, const V &variant)
+{
+ auto result = insertVariant<T1>(key, variant);
+ return result != end() ? result : insertVariant<T2, Args...>(key, variant);
+}
+
+template<typename T>
+T JsonObject::typedValue(const std::string_view key) const
+{
+ return fromJsonValue<T>(value(key));
+}
+
+template<typename T>
+std::optional<T> JsonObject::optionalValue(const std::string_view key) const
+{
+ const QJsonValue &val = value(key);
+ return val.isUndefined() ? std::nullopt : std::make_optional(fromJsonValue<T>(val));
+}
+
+template<typename T>
+LanguageClientValue<T> JsonObject::clientValue(const std::string_view key) const
+{
+ return LanguageClientValue<T>(value(key));
+}
+
+template<typename T>
+std::optional<LanguageClientValue<T>> JsonObject::optionalClientValue(const std::string_view key) const
+{
+ return contains(key) ? std::make_optional(clientValue<T>(key)) : std::nullopt;
+}
+
+template<typename T>
+QList<T> JsonObject::array(const std::string_view key) const
+{
+ if (const std::optional<QList<T>> &array = optionalArray<T>(key))
+ return *array;
+ qCDebug(conversionLog) << QString("Expected array under %1 in:")
+ .arg(QLatin1String(key.data())) << *this;
+ return {};
+}
+
+template<typename T>
+std::optional<QList<T>> JsonObject::optionalArray(const std::string_view key) const
+{
+ const QJsonValue &jsonValue = value(key);
+ if (jsonValue.isUndefined())
+ return std::nullopt;
+ return Utils::transform<QList<T>>(jsonValue.toArray(), &fromJsonValue<T>);
+}
+
+template<typename T>
+LanguageClientArray<T> JsonObject::clientArray(const std::string_view key) const
+{
+ return LanguageClientArray<T>(value(key));
+}
+
+template<typename T>
+std::optional<LanguageClientArray<T>> JsonObject::optionalClientArray(const std::string_view key) const
+{
+ const QJsonValue &val = value(key);
+ return !val.isUndefined() ? std::make_optional(LanguageClientArray<T>(value(key)))
+ : std::nullopt;
+}
+
+template<typename T>
+void JsonObject::insertArray(const std::string_view key, const QList<T> &array)
+{
+ QJsonArray jsonArray;
+ for (const T &item : array)
+ jsonArray.append(QJsonValue(item));
+ insert(key, jsonArray);
+}
+
+template<typename >
+void JsonObject::insertArray(const std::string_view key, const QList<JsonObject> &array)
+{
+ QJsonArray jsonArray;
+ for (const JsonObject &item : array)
+ jsonArray.append(item.m_jsonObject);
+ insert(key, jsonArray);
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/jsonrpcmessages.cpp b/src/shared/lsp/jsonrpcmessages.cpp
new file mode 100644
index 000000000..dbe354bc4
--- /dev/null
+++ b/src/shared/lsp/jsonrpcmessages.cpp
@@ -0,0 +1,107 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "jsonrpcmessages.h"
+
+#include "initializemessages.h"
+#include "languageserverprotocoltr.h"
+#include "lsputils.h"
+
+#include <QCoreApplication>
+#include <QObject>
+#include <QJsonDocument>
+#include <QTextCodec>
+
+namespace lsp {
+Q_LOGGING_CATEGORY(timingLog, "qtc.languageserverprotocol.timing", QtWarningMsg)
+
+constexpr const char CancelRequest::methodName[];
+
+QByteArray JsonRpcMessage::toRawData() const
+{
+ return QJsonDocument(m_jsonObject).toJson(QJsonDocument::Compact);
+}
+
+bool JsonRpcMessage::isValid(QString *errorMessage) const
+{
+ if (!m_parseError.isEmpty()) {
+ if (errorMessage)
+ *errorMessage = m_parseError;
+ return false;
+ }
+ return m_jsonObject[jsonRpcVersionKey] == "2.0";
+}
+
+const QJsonObject &JsonRpcMessage::toJsonObject() const
+{
+ return m_jsonObject;
+}
+
+JsonRpcMessage::JsonRpcMessage()
+{
+ // The language server protocol always uses “2.0” as the jsonrpc version
+ m_jsonObject[jsonRpcVersionKey] = "2.0";
+}
+
+constexpr int utf8mib = 106;
+
+static QString docType(const QJsonDocument &doc)
+{
+ if (doc.isArray())
+ return QString("array");
+ if (doc.isEmpty())
+ return QString("empty");
+ if (doc.isNull())
+ return QString("null");
+ if (doc.isObject())
+ return QString("object");
+ return {};
+}
+
+JsonRpcMessage::JsonRpcMessage(const BaseMessage &message)
+{
+ if (message.content.isEmpty())
+ return;
+ QByteArray content;
+ if (message.codec && message.codec->mibEnum() != utf8mib) {
+ QTextCodec *utf8 = QTextCodec::codecForMib(utf8mib);
+ if (utf8)
+ content = utf8->fromUnicode(message.codec->toUnicode(message.content));
+ }
+ if (content.isEmpty())
+ content = message.content;
+ QJsonParseError error = {0, QJsonParseError::NoError};
+ const QJsonDocument doc = QJsonDocument::fromJson(content, &error);
+ if (doc.isObject())
+ m_jsonObject = doc.object();
+ else if (doc.isNull())
+ m_parseError = Tr::tr("Could not parse JSON message: \"%1\".").arg(error.errorString());
+ else
+ m_parseError =
+ Tr::tr("Expected a JSON object, but got a JSON \"%1\" value.").arg(docType(doc));
+}
+
+JsonRpcMessage::JsonRpcMessage(const QJsonObject &jsonObject)
+ : m_jsonObject(jsonObject)
+{ }
+
+JsonRpcMessage::JsonRpcMessage(QJsonObject &&jsonObject)
+ : m_jsonObject(std::move(jsonObject))
+{ }
+
+QByteArray JsonRpcMessage::jsonRpcMimeType()
+{
+ return "application/vscode-jsonrpc";
+}
+
+CancelRequest::CancelRequest(const CancelParameter &params)
+ : Notification(methodName, params)
+{ }
+
+void logElapsedTime(const QString &method, const QElapsedTimer &t)
+{
+ qCDebug(timingLog) << "received server reply to" << method
+ << "after" << t.elapsed() << "ms";
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/jsonrpcmessages.h b/src/shared/lsp/jsonrpcmessages.h
new file mode 100644
index 000000000..a523dd5e1
--- /dev/null
+++ b/src/shared/lsp/jsonrpcmessages.h
@@ -0,0 +1,415 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "basemessage.h"
+#include "jsonkeys.h"
+#include "lsptypes.h"
+
+#include <QDebug>
+#include <QElapsedTimer>
+#include <QHash>
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QCoreApplication>
+#include <QUuid>
+
+#include <optional>
+#include <variant>
+
+namespace lsp {
+
+class JsonRpcMessage;
+
+class LANGUAGESERVERPROTOCOL_EXPORT MessageId : public std::variant<int, QString>
+{
+public:
+ MessageId() : variant(QString()) {}
+ explicit MessageId(int id) : variant(id) {}
+ explicit MessageId(const QString &id) : variant(id) {}
+ explicit MessageId(const QJsonValue &value)
+ {
+ if (value.isDouble())
+ emplace<int>(value.toInt());
+ else
+ emplace<QString>(value.toString());
+ }
+
+ operator QJsonValue() const
+ {
+ if (auto id = std::get_if<int>(this))
+ return *id;
+ if (auto id = std::get_if<QString>(this))
+ return *id;
+ return QJsonValue();
+ }
+
+ bool isValid() const
+ {
+ if (std::holds_alternative<int>(*this))
+ return true;
+ const QString *id = std::get_if<QString>(this);
+ QBS_ASSERT(id, return false);
+ return !id->isEmpty();
+ }
+
+ QString toString() const
+ {
+ if (auto id = std::get_if<QString>(this))
+ return *id;
+ if (auto id = std::get_if<int>(this))
+ return QString::number(*id);
+ return {};
+ }
+
+private:
+ friend size_t qHash(const MessageId &id)
+ {
+ if (std::holds_alternative<int>(id))
+ return QT_PREPEND_NAMESPACE(qHash(std::get<int>(id)));
+ if (std::holds_alternative<QString>(id))
+ return QT_PREPEND_NAMESPACE(qHash(std::get<QString>(id)));
+ return QT_PREPEND_NAMESPACE(qHash(0));
+ }
+
+ friend QDebug operator<<(QDebug stream, const MessageId &id)
+ {
+ if (std::holds_alternative<int>(id))
+ stream << std::get<int>(id);
+ else
+ stream << std::get<QString>(id);
+ return stream;
+ }
+};
+
+struct ResponseHandler
+{
+ MessageId id;
+ using Callback = std::function<void(const JsonRpcMessage &)>;
+ Callback callback;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT JsonRpcMessage
+{
+public:
+ JsonRpcMessage();
+ explicit JsonRpcMessage(const BaseMessage &message);
+ explicit JsonRpcMessage(const QJsonObject &jsonObject);
+ explicit JsonRpcMessage(QJsonObject &&jsonObject);
+
+ virtual ~JsonRpcMessage() = default;
+
+ static QByteArray jsonRpcMimeType();
+
+ QByteArray toRawData() const;
+ virtual bool isValid(QString *errorMessage) const;
+
+ const QJsonObject &toJsonObject() const;
+
+ const QString parseError() { return m_parseError; }
+
+ virtual std::optional<ResponseHandler> responseHandler() const { return std::nullopt; }
+
+ BaseMessage toBaseMessage() const
+ { return BaseMessage(jsonRpcMimeType(), toRawData()); }
+
+protected:
+ QJsonObject m_jsonObject;
+
+private:
+ QString m_parseError;
+};
+
+template <typename Params>
+class Notification : public JsonRpcMessage
+{
+public:
+ Notification(const QString &methodName, const Params &params)
+ {
+ setMethod(methodName);
+ setParams(params);
+ }
+
+ explicit Notification(const QJsonObject &jsonObject) : JsonRpcMessage(jsonObject) {}
+ explicit Notification(QJsonObject &&jsonObject) : JsonRpcMessage(std::move(jsonObject)) {}
+
+ QString method() const
+ { return fromJsonValue<QString>(m_jsonObject.value(methodKey)); }
+ void setMethod(const QString &method)
+ { m_jsonObject.insert(methodKey, method); }
+
+ using Parameters = Params;
+ std::optional<Params> params() const
+ {
+ const QJsonValue &params = m_jsonObject.value(paramsKey);
+ return params.isUndefined() ? std::nullopt : std::make_optional(Params(params));
+ }
+ void setParams(const Params &params)
+ { m_jsonObject.insert(paramsKey, QJsonValue(params)); }
+ void clearParams() { m_jsonObject.remove(paramsKey); }
+
+ bool isValid(QString *errorMessage) const override
+ {
+ return JsonRpcMessage::isValid(errorMessage)
+ && m_jsonObject.value(methodKey).isString()
+ && parametersAreValid(errorMessage);
+ }
+
+ virtual bool parametersAreValid(QString *errorMessage) const
+ {
+ if (auto parameter = params())
+ return parameter->isValid();
+ if (errorMessage)
+ *errorMessage = QCoreApplication::translate("QtC::LanguageServerProtocol",
+ "No parameters in \"%1\".").arg(method());
+ return false;
+ }
+};
+
+template <>
+class Notification<std::nullptr_t> : public JsonRpcMessage
+{
+public:
+ Notification() : Notification(QString()) {}
+ explicit Notification(const QString &methodName, const std::nullptr_t &/*params*/ = nullptr)
+ {
+ setMethod(methodName);
+ setParams(nullptr);
+ }
+ using JsonRpcMessage::JsonRpcMessage;
+
+ QString method() const
+ { return fromJsonValue<QString>(m_jsonObject.value(methodKey)); }
+ void setMethod(const QString &method)
+ { m_jsonObject.insert(methodKey, method); }
+
+ std::optional<std::nullptr_t> params() const
+ { return nullptr; }
+ void setParams(const std::nullptr_t &/*params*/)
+ { m_jsonObject.insert(paramsKey, QJsonValue::Null); }
+ void clearParams() { m_jsonObject.remove(paramsKey); }
+
+ bool isValid(QString *errorMessage) const override
+ {
+ return JsonRpcMessage::isValid(errorMessage)
+ && m_jsonObject.value(methodKey).isString()
+ && parametersAreValid(errorMessage);
+ }
+
+ virtual bool parametersAreValid(QString * /*errorMessage*/) const
+ { return true; }
+};
+
+template <typename Error>
+class ResponseError : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ int code() const { return typedValue<int>(codeKey); }
+ void setCode(int code) { insert(codeKey, code); }
+
+ QString message() const { return typedValue<QString>(messageKey); }
+ void setMessage(const QString &message) { insert(messageKey, message); }
+
+ std::optional<Error> data() const { return optionalValue<Error>(dataKey); }
+ void setData(const Error &data) { insert(dataKey, data); }
+ void clearData() { remove(dataKey); }
+
+ bool isValid() const override { return contains(codeKey) && contains(messageKey); }
+
+ QString toString() const { return errorCodesToString(code()) + ": " + message(); }
+
+ // predefined error codes
+ enum ErrorCodes {
+ // Defined by JSON RPC
+ ParseError = -32700,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+ serverErrorStart = -32099,
+ serverErrorEnd = -32000,
+ ServerNotInitialized = -32002,
+ UnknownErrorCode = -32001,
+
+ // Defined by the protocol.
+ RequestCancelled = -32800
+ };
+
+#define CASE_ERRORCODES(x) case ErrorCodes::x: return QLatin1String(#x)
+ static QString errorCodesToString(int code)
+ {
+ switch (code) {
+ CASE_ERRORCODES(ParseError);
+ CASE_ERRORCODES(InvalidRequest);
+ CASE_ERRORCODES(MethodNotFound);
+ CASE_ERRORCODES(InvalidParams);
+ CASE_ERRORCODES(InternalError);
+ CASE_ERRORCODES(serverErrorStart);
+ CASE_ERRORCODES(serverErrorEnd);
+ CASE_ERRORCODES(ServerNotInitialized);
+ CASE_ERRORCODES(RequestCancelled);
+ default:
+ return QCoreApplication::translate("QtC::LanguageClient", "Error %1").arg(code);
+ }
+ }
+#undef CASE_ERRORCODES
+};
+
+template <typename Result, typename ErrorDataType>
+class Response : public JsonRpcMessage
+{
+public:
+ explicit Response(const MessageId &id) { setId(id); }
+ using JsonRpcMessage::JsonRpcMessage;
+
+ MessageId id() const
+ { return MessageId(m_jsonObject.value(idKey)); }
+ void setId(MessageId id)
+ { this->m_jsonObject.insert(idKey, id); }
+
+ std::optional<Result> result() const
+ {
+ const QJsonValue &result = m_jsonObject.value(resultKey);
+ if (result.isUndefined())
+ return std::nullopt;
+ return std::make_optional(Result(result));
+ }
+ void setResult(const Result &result) { m_jsonObject.insert(resultKey, QJsonValue(result)); }
+ void clearResult() { m_jsonObject.remove(resultKey); }
+
+ using Error = ResponseError<ErrorDataType>;
+ std::optional<Error> error() const
+ {
+ const QJsonValue &val = m_jsonObject.value(errorKey);
+ return val.isUndefined() ? std::nullopt : std::make_optional(fromJsonValue<Error>(val));
+ }
+ void setError(const Error &error)
+ {
+ m_jsonObject.insert(errorKey, QJsonValue(error));
+ }
+ void clearError() { m_jsonObject.remove(errorKey); }
+
+ bool isValid(QString *errorMessage) const override
+ { return JsonRpcMessage::isValid(errorMessage) && id().isValid(); }
+};
+
+template<typename ErrorDataType>
+class Response<std::nullptr_t, ErrorDataType> : public JsonRpcMessage
+{
+public:
+ explicit Response(const MessageId &id) { setId(id); }
+ using JsonRpcMessage::JsonRpcMessage;
+
+ MessageId id() const
+ { return MessageId(m_jsonObject.value(idKey)); }
+ void setId(MessageId id)
+ { this->m_jsonObject.insert(idKey, id); }
+
+ std::optional<std::nullptr_t> result() const
+ {
+ return m_jsonObject.value(resultKey).isNull() ? std::make_optional(nullptr) : std::nullopt;
+ }
+ void setResult(const std::nullptr_t &) { m_jsonObject.insert(resultKey, QJsonValue::Null); }
+ void clearResult() { m_jsonObject.remove(resultKey); }
+
+ using Error = ResponseError<ErrorDataType>;
+ std::optional<Error> error() const
+ {
+ const QJsonValue &val = m_jsonObject.value(errorKey);
+ return val.isUndefined() ? std::nullopt : std::make_optional(fromJsonValue<Error>(val));
+ }
+ void setError(const Error &error)
+ { m_jsonObject.insert(errorKey, QJsonValue(error)); }
+ void clearError() { m_jsonObject.remove(errorKey); }
+
+ bool isValid(QString *errorMessage) const override
+ { return JsonRpcMessage::isValid(errorMessage) && id().isValid(); }
+};
+
+void LANGUAGESERVERPROTOCOL_EXPORT logElapsedTime(const QString &method, const QElapsedTimer &t);
+
+template <typename Result, typename ErrorDataType, typename Params>
+class Request : public Notification<Params>
+{
+public:
+ Request(const QString &methodName, const Params &params)
+ : Notification<Params>(methodName, params)
+ { setId(MessageId(QUuid::createUuid().toString())); }
+ explicit Request(const QJsonObject &jsonObject) : Notification<Params>(jsonObject) { }
+ explicit Request(QJsonObject &&jsonObject) : Notification<Params>(std::move(jsonObject)) { }
+
+ MessageId id() const
+ { return MessageId(JsonRpcMessage::m_jsonObject.value(idKey)); }
+ void setId(const MessageId &id)
+ { JsonRpcMessage::m_jsonObject.insert(idKey, id); }
+
+ using Response = lsp::Response<Result, ErrorDataType>;
+ using ResponseCallback = std::function<void(Response)>;
+ void setResponseCallback(const ResponseCallback &callback)
+ { m_callBack = callback; }
+
+ std::optional<ResponseHandler> responseHandler() const final
+ {
+ QElapsedTimer timer;
+ timer.start();
+ auto callback = [callback = m_callBack, method = this->method(), t = std::move(timer)]
+ (const JsonRpcMessage &message) {
+ if (!callback)
+ return;
+ logElapsedTime(method, t);
+
+ callback(Response(message.toJsonObject()));
+ };
+ return std::make_optional(ResponseHandler{id(), callback});
+ }
+
+ bool isValid(QString *errorMessage) const override
+ {
+ if (!Notification<Params>::isValid(errorMessage))
+ return false;
+ if (id().isValid())
+ return true;
+ if (errorMessage)
+ *errorMessage = QCoreApplication::translate("QtC::LanguageServerProtocol",
+ "No ID set in \"%1\".").arg(this->method());
+ return false;
+ }
+
+private:
+ ResponseCallback m_callBack;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CancelParameter : public JsonObject
+{
+public:
+ explicit CancelParameter(const MessageId &id) { setId(id); }
+ CancelParameter() = default;
+ using JsonObject::JsonObject;
+
+ MessageId id() const { return typedValue<MessageId>(idKey); }
+ void setId(const MessageId &id) { insert(idKey, id); }
+
+ bool isValid() const override { return contains(idKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CancelRequest : public Notification<CancelParameter>
+{
+public:
+ explicit CancelRequest(const CancelParameter &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "$/cancelRequest";
+};
+
+} // namespace lsp
+
+template <typename Error>
+inline QDebug operator<<(QDebug stream, const lsp::ResponseError<Error> &error)
+{
+ stream.nospace() << error.toString();
+ return stream;
+}
+
+Q_DECLARE_METATYPE(lsp::JsonRpcMessage)
diff --git a/src/shared/lsp/languagefeatures.cpp b/src/shared/lsp/languagefeatures.cpp
new file mode 100644
index 000000000..8ca771f68
--- /dev/null
+++ b/src/shared/lsp/languagefeatures.cpp
@@ -0,0 +1,399 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "languagefeatures.h"
+
+#include <cstddef>
+
+namespace lsp {
+
+constexpr const char HoverRequest::methodName[];
+constexpr const char GotoDefinitionRequest::methodName[];
+constexpr const char GotoTypeDefinitionRequest::methodName[];
+constexpr const char GotoImplementationRequest::methodName[];
+constexpr const char FindReferencesRequest::methodName[];
+constexpr const char DocumentHighlightsRequest::methodName[];
+constexpr const char DocumentSymbolsRequest::methodName[];
+constexpr const char CodeActionRequest::methodName[];
+constexpr const char CodeLensRequest::methodName[];
+constexpr const char CodeLensResolveRequest::methodName[];
+constexpr const char DocumentLinkRequest::methodName[];
+constexpr const char DocumentLinkResolveRequest::methodName[];
+constexpr const char DocumentColorRequest::methodName[];
+constexpr const char ColorPresentationRequest::methodName[];
+constexpr const char DocumentFormattingRequest::methodName[];
+constexpr const char DocumentRangeFormattingRequest::methodName[];
+constexpr const char DocumentOnTypeFormattingRequest::methodName[];
+constexpr const char RenameRequest::methodName[];
+constexpr const char SignatureHelpRequest::methodName[];
+constexpr const char PrepareRenameRequest::methodName[];
+
+HoverContent Hover::content() const
+{
+ return HoverContent(value(contentsKey));
+}
+
+void Hover::setContent(const HoverContent &content)
+{
+ if (auto val = std::get_if<MarkedString>(&content))
+ insert(contentsKey, *val);
+ else if (auto val = std::get_if<MarkupContent>(&content))
+ insert(contentsKey, *val);
+ else if (auto val = std::get_if<QList<MarkedString>>(&content))
+ insert(contentsKey, LanguageClientArray<MarkedString>(*val).toJson());
+ else
+ QBS_ASSERT("LanguageClient Using unknown type Hover::setContent",;);
+}
+
+HoverRequest::HoverRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{ }
+
+std::optional<MarkupOrString> ParameterInformation::documentation() const
+{
+ QJsonValue documentation = value(documentationKey);
+ if (documentation.isUndefined())
+ return std::nullopt;
+ return MarkupOrString(documentation);
+}
+
+GotoDefinitionRequest::GotoDefinitionRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{ }
+
+GotoTypeDefinitionRequest::GotoTypeDefinitionRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{ }
+
+GotoImplementationRequest::GotoImplementationRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{ }
+
+FindReferencesRequest::FindReferencesRequest(const ReferenceParams &params)
+ : Request(methodName, params)
+{ }
+
+DocumentHighlightsRequest::DocumentHighlightsRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{ }
+
+DocumentSymbolsRequest::DocumentSymbolsRequest(const DocumentSymbolParams &params)
+ : Request(methodName, params)
+{ }
+
+std::optional<QList<CodeActionKind> > CodeActionParams::CodeActionContext::only() const
+{
+ return optionalArray<CodeActionKind>(onlyKey);
+}
+
+void CodeActionParams::CodeActionContext::setOnly(const QList<CodeActionKind> &only)
+{
+ insertArray(onlyKey, only);
+}
+
+CodeActionRequest::CodeActionRequest(const CodeActionParams &params)
+ : Request(methodName, params)
+{ }
+
+CodeLensRequest::CodeLensRequest(const CodeLensParams &params)
+ : Request(methodName, params)
+{ }
+
+CodeLensResolveRequest::CodeLensResolveRequest(const CodeLens &params)
+ : Request(methodName, params)
+{ }
+
+DocumentLinkRequest::DocumentLinkRequest(const DocumentLinkParams &params)
+ : Request(methodName, params)
+{ }
+
+DocumentLinkResolveRequest::DocumentLinkResolveRequest(const DocumentLink &params)
+ : Request(methodName, params)
+{ }
+
+DocumentColorRequest::DocumentColorRequest(const DocumentColorParams &params)
+ : Request(methodName, params)
+{ }
+
+ColorPresentationRequest::ColorPresentationRequest(const ColorPresentationParams &params)
+ : Request(methodName, params)
+{ }
+
+QHash<QString, DocumentFormattingProperty> FormattingOptions::properties() const
+{
+ QHash<QString, DocumentFormattingProperty> ret;
+ for (const QString &key : keys()) {
+ if (key == tabSizeKey || key == insertSpaceKey)
+ continue;
+ QJsonValue property = value(key.toStdString());
+ if (property.isBool())
+ ret[key] = property.toBool();
+ if (property.isDouble())
+ ret[key] = property.toDouble();
+ if (property.isString())
+ ret[key] = property.toString();
+ }
+ return ret;
+}
+
+void FormattingOptions::setProperty(const std::string_view key, const DocumentFormattingProperty &property)
+{
+ using namespace std;
+ if (auto val = get_if<double>(&property))
+ insert(key, *val);
+ else if (auto val = get_if<QString>(&property))
+ insert(key, *val);
+ else if (auto val = get_if<bool>(&property))
+ insert(key, *val);
+}
+
+DocumentFormattingRequest::DocumentFormattingRequest(const DocumentFormattingParams &params)
+ : Request(methodName, params)
+{ }
+
+DocumentRangeFormattingRequest::DocumentRangeFormattingRequest(
+ const DocumentRangeFormattingParams &params)
+ : Request(methodName, params)
+{ }
+
+bool DocumentOnTypeFormattingParams::isValid() const
+{
+ return contains(textDocumentKey) && contains(positionKey) && contains(chKey)
+ && contains(optionsKey);
+}
+
+DocumentOnTypeFormattingRequest::DocumentOnTypeFormattingRequest(
+ const DocumentOnTypeFormattingParams &params)
+ : Request(methodName, params)
+{ }
+
+PrepareRenameRequest::PrepareRenameRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{ }
+
+bool RenameParams::isValid() const
+{
+ return contains(textDocumentKey) && contains(positionKey) && contains(newNameKey);
+}
+
+RenameRequest::RenameRequest(const RenameParams &params)
+ : Request(methodName, params)
+{ }
+
+std::optional<DocumentUri> DocumentLink::target() const
+{
+ if (std::optional<QString> optionalTarget = optionalValue<QString>(targetKey))
+ return std::make_optional(DocumentUri::fromProtocol(*optionalTarget));
+ return std::nullopt;
+}
+
+std::optional<QJsonValue> DocumentLink::data() const
+{
+ return contains(dataKey) ? std::make_optional(value(dataKey)) : std::nullopt;
+}
+
+TextDocumentParams::TextDocumentParams()
+ : TextDocumentParams(TextDocumentIdentifier())
+{ }
+
+TextDocumentParams::TextDocumentParams(const TextDocumentIdentifier &identifier)
+ : JsonObject()
+{
+ setTextDocument(identifier);
+}
+
+GotoResult::GotoResult(const QJsonValue &value)
+{
+ if (value.isArray()) {
+ QList<Location> locations;
+ for (auto arrayValue : value.toArray()) {
+ if (arrayValue.isObject())
+ locations.append(Location(arrayValue.toObject()));
+ }
+ emplace<QList<Location>>(locations);
+ } else if (value.isObject()) {
+ emplace<Location>(value.toObject());
+ } else {
+ emplace<std::nullptr_t>(nullptr);
+ }
+}
+
+template<typename Symbol>
+QList<Symbol> documentSymbolsResultArray(const QJsonArray &array)
+{
+ QList<Symbol> ret;
+ for (const auto &arrayValue : array) {
+ if (arrayValue.isObject())
+ ret << Symbol(arrayValue.toObject());
+ }
+ return ret;
+}
+
+DocumentSymbolsResult::DocumentSymbolsResult(const QJsonValue &value)
+{
+ if (value.isArray()) {
+ QJsonArray array = value.toArray();
+ if (array.isEmpty()) {
+ *this = QList<SymbolInformation>();
+ } else {
+ QJsonObject arrayObject = array.first().toObject();
+ if (arrayObject.contains(rangeKey))
+ *this = documentSymbolsResultArray<DocumentSymbol>(array);
+ else
+ *this = documentSymbolsResultArray<SymbolInformation>(array);
+ }
+ } else {
+ *this = nullptr;
+ }
+}
+
+DocumentHighlightsResult::DocumentHighlightsResult(const QJsonValue &value)
+{
+ if (value.isArray()) {
+ QList<DocumentHighlight> highlights;
+ for (auto arrayValue : value.toArray()) {
+ if (arrayValue.isObject())
+ highlights.append(DocumentHighlight(arrayValue.toObject()));
+ }
+ *this = highlights;
+ } else {
+ *this = nullptr;
+ }
+}
+
+MarkedString::MarkedString(const QJsonValue &value)
+{
+ if (value.isObject())
+ emplace<MarkedLanguageString>(MarkedLanguageString(value.toObject()));
+ else
+ emplace<QString>(value.toString());
+}
+
+bool MarkedString::isValid() const
+{
+ if (auto markedLanguageString = std::get_if<MarkedLanguageString>(this))
+ return markedLanguageString->isValid();
+ return true;
+}
+
+MarkedString::operator QJsonValue() const
+{
+ if (auto val = std::get_if<QString>(this))
+ return *val;
+ if (auto val = std::get_if<MarkedLanguageString>(this))
+ return QJsonValue(*val);
+ return {};
+}
+
+HoverContent::HoverContent(const QJsonValue &value)
+{
+ if (value.isArray()) {
+ emplace<QList<MarkedString>>(LanguageClientArray<MarkedString>(value).toList());
+ } else if (value.isObject()) {
+ const QJsonObject &object = value.toObject();
+ MarkedLanguageString markedLanguageString(object);
+ if (markedLanguageString.isValid())
+ emplace<MarkedString>(markedLanguageString);
+ else
+ emplace<MarkupContent>(MarkupContent(object));
+ } else if (value.isString()) {
+ emplace<MarkedString>(MarkedString(value.toString()));
+ }
+}
+
+bool HoverContent::isValid() const
+{
+ if (std::holds_alternative<MarkedString>(*this))
+ return std::get<MarkedString>(*this).isValid();
+ return true;
+}
+
+DocumentFormattingProperty::DocumentFormattingProperty(const QJsonValue &value)
+{
+ if (value.isBool())
+ *this = value.toBool();
+ if (value.isDouble())
+ *this = value.toDouble();
+ if (value.isString())
+ *this = value.toString();
+}
+
+SignatureHelpRequest::SignatureHelpRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{ }
+
+CodeActionResult::CodeActionResult(const QJsonValue &val)
+{
+ using ResultArray = QList<std::variant<Command, CodeAction>>;
+ if (val.isArray()) {
+ const QJsonArray array = val.toArray();
+ ResultArray result;
+ for (const QJsonValue &val : array) {
+ if (val.toObject().value(commandKey).isString()) {
+ const Command command(val);
+ if (command.isValid())
+ result << command;
+ } else {
+ const CodeAction action(val);
+ if (action.isValid())
+ result << action;
+ }
+ }
+ emplace<ResultArray>(result);
+ return;
+ }
+ emplace<std::nullptr_t>(nullptr);
+}
+
+PrepareRenameResult::PrepareRenameResult()
+ : std::variant<PlaceHolderResult, Range, std::nullptr_t>(nullptr)
+{}
+
+PrepareRenameResult::PrepareRenameResult(
+ const std::variant<PlaceHolderResult, Range, std::nullptr_t> &val)
+ : std::variant<PlaceHolderResult, Range, std::nullptr_t>(val)
+{}
+
+PrepareRenameResult::PrepareRenameResult(const PlaceHolderResult &val)
+ : std::variant<PlaceHolderResult, Range, std::nullptr_t>(val)
+
+{}
+
+PrepareRenameResult::PrepareRenameResult(const Range &val)
+ : std::variant<PlaceHolderResult, Range, std::nullptr_t>(val)
+{}
+
+PrepareRenameResult::PrepareRenameResult(const QJsonValue &val)
+{
+ if (val.isNull()) {
+ emplace<std::nullptr_t>(nullptr);
+ } else if (val.isObject()) {
+ const QJsonObject object = val.toObject();
+ if (object.keys().contains(rangeKey))
+ emplace<PlaceHolderResult>(PlaceHolderResult(object));
+ else
+ emplace<Range>(Range(object));
+ }
+}
+
+std::optional<QJsonValue> CodeLens::data() const
+{
+ return contains(dataKey) ? std::make_optional(value(dataKey)) : std::nullopt;
+}
+
+HoverResult::HoverResult(const QJsonValue &value)
+{
+ if (value.isObject())
+ emplace<Hover>(Hover(value.toObject()));
+ else
+ emplace<std::nullptr_t>(nullptr);
+}
+
+bool HoverResult::isValid() const
+{
+ if (auto hover = std::get_if<Hover>(this))
+ return hover->isValid();
+ return true;
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/languagefeatures.h b/src/shared/lsp/languagefeatures.h
new file mode 100644
index 000000000..13e3e9857
--- /dev/null
+++ b/src/shared/lsp/languagefeatures.h
@@ -0,0 +1,836 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+
+namespace lsp {
+
+/**
+ * MarkedString can be used to render human readable text. It is either a markdown string
+ * or a code-block that provides a language and a code snippet. The language identifier
+ * is semantically equal to the optional language identifier in fenced code blocks in GitHub
+ * issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting
+ *
+ * The pair of a language and a value is an equivalent to markdown:
+ * ```${language}
+ * ${value}
+ * ```
+ *
+ * Note that markdown strings will be sanitized - that means html will be escaped.
+* @deprecated use MarkupContent instead.
+*/
+class LANGUAGESERVERPROTOCOL_EXPORT MarkedLanguageString : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString language() const { return typedValue<QString>(languageKey); }
+ void setLanguage(const QString &language) { insert(languageKey, language); }
+
+ QString value() const { return typedValue<QString>(valueKey); }
+ void setValue(const QString &value) { insert(valueKey, value); }
+
+ bool isValid() const override { return contains(languageKey) && contains(valueKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT MarkedString
+ : public std::variant<QString, MarkedLanguageString>
+{
+public:
+ MarkedString() = default;
+ explicit MarkedString(const MarkedLanguageString &other)
+ : variant(other)
+ {}
+ explicit MarkedString(const QString &other)
+ : variant(other)
+ {}
+ explicit MarkedString(const QJsonValue &value);
+
+ bool isValid() const;
+ operator QJsonValue() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT HoverContent
+ : public std::variant<MarkedString, QList<MarkedString>, MarkupContent>
+{
+public:
+ HoverContent() = default;
+ explicit HoverContent(const MarkedString &other) : variant(other) {}
+ explicit HoverContent(const QList<MarkedString> &other) : variant(other) {}
+ explicit HoverContent(const MarkupContent &other) : variant(other) {}
+ explicit HoverContent(const QJsonValue &value);
+ bool isValid() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT Hover : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ HoverContent content() const;
+ void setContent(const HoverContent &content);
+
+ std::optional<Range> range() const { return optionalValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+ void clearRange() { remove(rangeKey); }
+
+ bool isValid() const override { return contains(contentsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT HoverResult : public std::variant<Hover, std::nullptr_t>
+{
+public:
+ HoverResult() : variant(nullptr) {}
+ explicit HoverResult(const Hover &hover) : variant(hover) {}
+ explicit HoverResult(const QJsonValue &value);
+ bool isValid() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT HoverRequest
+ : public Request<HoverResult, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit HoverRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/hover";
+};
+
+/**
+ * Represents a parameter of a callable-signature. A parameter can
+ * have a label and a doc-comment.
+ */
+class LANGUAGESERVERPROTOCOL_EXPORT ParameterInformation : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString label() const { return typedValue<QString>(labelKey); }
+ void setLabel(const QString &label) { insert(labelKey, label); }
+
+ std::optional<MarkupOrString> documentation() const;
+ void setDocumentation(const MarkupOrString &documentation)
+ { insert(documentationKey, documentation.toJson()); }
+ void clearDocumentation() { remove(documentationKey); }
+
+ bool isValid() const override { return contains(labelKey); }
+};
+
+/**
+ * Represents the signature of something callable. A signature
+ * can have a label, like a function-name, a doc-comment, and
+ * a set of parameters.
+ */
+class LANGUAGESERVERPROTOCOL_EXPORT SignatureInformation : public ParameterInformation
+{
+public:
+ using ParameterInformation::ParameterInformation;
+
+ std::optional<QList<ParameterInformation>> parameters() const
+ { return optionalArray<ParameterInformation>(parametersKey); }
+ void setParameters(const QList<ParameterInformation> &parameters)
+ { insertArray(parametersKey, parameters); }
+ void clearParameters() { remove(parametersKey); }
+
+ std::optional<int> activeParameter() const { return optionalValue<int>(activeParameterKey); }
+ void setActiveParameter(int activeParameter) { insert(activeParameterKey, activeParameter); }
+ void clearActiveParameter() { remove(activeParameterKey); }
+};
+
+/**
+ * Signature help represents the signature of something
+ * callable. There can be multiple signature but only one
+ * active and only one active parameter.
+ */
+class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelp : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /// One or more signatures.
+ QList<SignatureInformation> signatures() const
+ { return array<SignatureInformation>(signaturesKey); }
+ void setSignatures(const QList<SignatureInformation> &signatures)
+ { insertArray(signaturesKey, signatures); }
+
+ /**
+ * The active signature. If omitted or the value lies outside the
+ * range of `signatures` the value defaults to zero or is ignored if
+ * `signatures.length === 0`. Whenever possible implementors should
+ * make an active decision about the active signature and shouldn't
+ * rely on a default value.
+ * In future version of the protocol this property might become
+ * mandatory to better express this.
+ */
+ std::optional<int> activeSignature() const { return optionalValue<int>(activeSignatureKey); }
+ void setActiveSignature(int activeSignature) { insert(activeSignatureKey, activeSignature); }
+ void clearActiveSignature() { remove(activeSignatureKey); }
+
+ /**
+ * The active parameter of the active signature. If omitted or the value
+ * lies outside the range of `signatures[activeSignature].parameters`
+ * defaults to 0 if the active signature has parameters. If
+ * the active signature has no parameters it is ignored.
+ * In future version of the protocol this property might become
+ * mandatory to better express the active parameter if the
+ * active signature does have any.
+ */
+ std::optional<int> activeParameter() const { return optionalValue<int>(activeParameterKey); }
+ void setActiveParameter(int activeParameter) { insert(activeParameterKey, activeParameter); }
+ void clearActiveParameter() { remove(activeParameterKey); }
+
+ bool isValid() const override { return contains(signaturesKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelpRequest
+ : public Request<LanguageClientValue<SignatureHelp>, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit SignatureHelpRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/signatureHelp";
+};
+
+/// The result of a goto request can either be a location, a list of locations or null
+class LANGUAGESERVERPROTOCOL_EXPORT GotoResult
+ : public std::variant<Location, QList<Location>, std::nullptr_t>
+{
+public:
+ explicit GotoResult(const QJsonValue &value);
+ using variant::variant;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT GotoDefinitionRequest
+ : public Request<GotoResult, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit GotoDefinitionRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/definition";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT GotoTypeDefinitionRequest : public Request<
+ GotoResult, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit GotoTypeDefinitionRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/typeDefinition";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT GotoImplementationRequest : public Request<
+ GotoResult, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit GotoImplementationRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/implementation";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ReferenceParams : public TextDocumentPositionParams
+{
+public:
+ using TextDocumentPositionParams::TextDocumentPositionParams;
+
+ class ReferenceContext : public JsonObject
+ {
+ public:
+ explicit ReferenceContext(bool includeDeclaration)
+ { setIncludeDeclaration(includeDeclaration); }
+ ReferenceContext() = default;
+ using JsonObject::JsonObject;
+ bool includeDeclaration() const { return typedValue<bool>(includeDeclarationKey); }
+ void setIncludeDeclaration(bool includeDeclaration)
+ { insert(includeDeclarationKey, includeDeclaration); }
+
+ bool isValid() const override { return contains(includeDeclarationKey); }
+ };
+
+ ReferenceContext context() const { return typedValue<ReferenceContext>(contextKey); }
+ void setContext(const ReferenceContext &context) { insert(contextKey, context); }
+
+ bool isValid() const override
+ { return TextDocumentPositionParams::isValid() && contains(contextKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT FindReferencesRequest : public Request<
+ LanguageClientArray<Location>, std::nullptr_t, ReferenceParams>
+{
+public:
+ explicit FindReferencesRequest(const ReferenceParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/references";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlight : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ enum DocumentHighlightKind {
+ Text = 1,
+ Read = 2,
+ Write = 3
+ };
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ std::optional<int> kind() const { return optionalValue<int>(kindKey); }
+ void setKind(int kind) { insert(kindKey, kind); }
+ void clearKind() { remove(kindKey); }
+
+ bool isValid() const override { return contains(rangeKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlightsResult
+ : public std::variant<QList<DocumentHighlight>, std::nullptr_t>
+{
+public:
+ using variant::variant;
+ DocumentHighlightsResult() : variant(nullptr) {}
+ explicit DocumentHighlightsResult(const QJsonValue &value);
+ using variant::operator=;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentHighlightsRequest : public Request<
+ DocumentHighlightsResult, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit DocumentHighlightsRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/documentHighlight";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentParams : public JsonObject
+{
+public:
+ TextDocumentParams();
+ explicit TextDocumentParams(const TextDocumentIdentifier &identifier);
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ bool isValid() const override { return contains(textDocumentKey); }
+};
+
+using DocumentSymbolParams = TextDocumentParams;
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentSymbolsResult
+ : public std::variant<QList<SymbolInformation>, QList<DocumentSymbol>, std::nullptr_t>
+{
+public:
+ using variant::variant;
+ DocumentSymbolsResult() : variant(nullptr) {}
+ explicit DocumentSymbolsResult(const QJsonValue &value);
+ DocumentSymbolsResult(const DocumentSymbolsResult &other) : variant(other) {}
+ DocumentSymbolsResult(DocumentSymbolsResult &&other) : variant(std::move(other)) {}
+
+ using variant::operator=;
+ DocumentSymbolsResult &operator =(DocumentSymbolsResult &&other)
+ {
+ variant::operator=(std::move(other));
+ return *this;
+ }
+ DocumentSymbolsResult &operator =(const DocumentSymbolsResult &other)
+ {
+ variant::operator=(other);
+ return *this;
+ }
+ // Needed to make it usable in Qt 6 signals.
+ bool operator<(const DocumentSymbolsResult &other) const = delete;
+};
+
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentSymbolsRequest
+ : public Request<DocumentSymbolsResult, std::nullptr_t, DocumentSymbolParams>
+{
+public:
+ explicit DocumentSymbolsRequest(const DocumentSymbolParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/documentSymbol";
+};
+
+/**
+ * The kind of a code action.
+ *
+ * Kinds are a hierarchical list of identifiers separated by `.`, e.g. `"refactor.extract.function"`.
+ *
+ * The set of kinds is open and client needs to announce the kinds it supports to the server during
+ * initialization.
+ */
+
+using CodeActionKind = QString;
+
+/**
+ * A set of predefined code action kinds
+ */
+
+namespace CodeActionKinds {
+constexpr char QuickFix[] = "quickfix";
+constexpr char Refactor[] = "refactor";
+constexpr char RefactorExtract[] = "refactor.extract";
+constexpr char RefactorInline[] = "refactor.inline";
+constexpr char RefactorRewrite[] = "refactor.rewrite";
+constexpr char Source[] = "source";
+constexpr char SourceOrganizeImports[] = "source.organizeImports";
+}
+
+class LANGUAGESERVERPROTOCOL_EXPORT CodeActionParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT CodeActionContext : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ QList<Diagnostic> diagnostics() const { return array<Diagnostic>(diagnosticsKey); }
+ void setDiagnostics(const QList<Diagnostic> &diagnostics)
+ { insertArray(diagnosticsKey, diagnostics); }
+
+ std::optional<QList<CodeActionKind>> only() const;
+ void setOnly(const QList<CodeActionKind> &only);
+ void clearOnly() { remove(onlyKey); }
+
+ bool isValid() const override { return contains(diagnosticsKey); }
+ };
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ CodeActionContext context() const { return typedValue<CodeActionContext>(contextKey); }
+ void setContext(const CodeActionContext &context) { insert(contextKey, context); }
+
+ bool isValid() const override
+ { return contains(textDocumentKey) && contains(rangeKey) && contains(contextKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CodeAction : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString title() const { return typedValue<QString>(titleKey); }
+ void setTitle(QString title) { insert(titleKey, title); }
+
+ std::optional<CodeActionKind> kind() const { return optionalValue<CodeActionKind>(kindKey); }
+ void setKind(const CodeActionKind &kind) { insert(kindKey, kind); }
+ void clearKind() { remove(kindKey); }
+
+ std::optional<QList<Diagnostic>> diagnostics() const
+ { return optionalArray<Diagnostic>(diagnosticsKey); }
+ void setDiagnostics(const QList<Diagnostic> &diagnostics)
+ { insertArray(diagnosticsKey, diagnostics); }
+ void clearDiagnostics() { remove(diagnosticsKey); }
+
+ std::optional<WorkspaceEdit> edit() const { return optionalValue<WorkspaceEdit>(editKey); }
+ void setEdit(const WorkspaceEdit &edit) { insert(editKey, edit); }
+ void clearEdit() { remove(editKey); }
+
+ std::optional<Command> command() const { return optionalValue<Command>(commandKey); }
+ void setCommand(const Command &command) { insert(commandKey, command); }
+ void clearCommand() { remove(commandKey); }
+
+ bool isValid() const override { return contains(titleKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CodeActionResult
+ : public std::variant<QList<std::variant<Command, CodeAction>>, std::nullptr_t>
+{
+public:
+ using variant::variant;
+ explicit CodeActionResult(const QJsonValue &val);
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CodeActionRequest : public Request<
+ CodeActionResult, std::nullptr_t, CodeActionParams>
+{
+public:
+ explicit CodeActionRequest(const CodeActionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/codeAction";
+};
+
+using CodeLensParams = TextDocumentParams;
+
+class LANGUAGESERVERPROTOCOL_EXPORT CodeLens : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ std::optional<Command> command() const { return optionalValue<Command>(commandKey); }
+ void setCommand(const Command &command) { insert(commandKey, command); }
+ void clearCommand() { remove(commandKey); }
+
+ std::optional<QJsonValue> data() const;
+ void setData(const QJsonValue &data) { insert(dataKey, data); }
+ void clearData() { remove(dataKey); }
+
+ bool isValid() const override { return contains(rangeKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CodeLensRequest : public Request<
+ LanguageClientArray<CodeLens>, std::nullptr_t, CodeLensParams>
+{
+public:
+ explicit CodeLensRequest(const CodeLensParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/codeLens";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CodeLensResolveRequest : public Request<
+ CodeLens, std::nullptr_t, CodeLens>
+{
+public:
+ explicit CodeLensResolveRequest(const CodeLens &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "codeLens/resolve";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentLink : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ std::optional<DocumentUri> target() const;
+ void setTarget(const DocumentUri &target) { insert(targetKey, target.toString()); }
+ void clearTarget() { remove(targetKey); }
+
+ std::optional<QJsonValue> data() const;
+ void setData(const QJsonValue &data) { insert(dataKey, data); }
+ void clearData() { remove(dataKey); }
+
+ bool isValid() const override { return contains(rangeKey); }
+};
+
+using DocumentLinkParams = TextDocumentParams;
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentLinkRequest : public Request<
+ LanguageClientValue<DocumentLink>, std::nullptr_t, DocumentLinkParams>
+{
+public:
+ explicit DocumentLinkRequest(const DocumentLinkParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/documentLink";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentLinkResolveRequest : public Request<
+ DocumentLink, std::nullptr_t, DocumentLink>
+{
+public:
+ explicit DocumentLinkResolveRequest(const DocumentLink &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "documentLink/resolve";
+};
+
+using DocumentColorParams = TextDocumentParams;
+
+class LANGUAGESERVERPROTOCOL_EXPORT Color : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ double red() const { return typedValue<double>(redKey); }
+ void setRed(double red) { insert(redKey, red); }
+
+ double green() const { return typedValue<double>(greenKey); }
+ void setGreen(double green) { insert(greenKey, green); }
+
+ double blue() const { return typedValue<double>(blueKey); }
+ void setBlue(double blue) { insert(blueKey, blue); }
+
+ double alpha() const { return typedValue<double>(alphaKey); }
+ void setAlpha(double alpha) { insert(alphaKey, alpha); }
+
+ bool isValid() const override
+ { return contains(redKey) && contains(greenKey) && contains(blueKey) && contains(alphaKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ColorInformation : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(Range range) { insert(rangeKey, range); }
+
+ Color color() const { return typedValue<Color>(colorKey); }
+ void setColor(const Color &color) { insert(colorKey, color); }
+
+ bool isValid() const override { return contains(rangeKey) && contains(colorKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentColorRequest : public Request<
+ QList<ColorInformation>, std::nullptr_t, DocumentColorParams>
+{
+public:
+ explicit DocumentColorRequest(const DocumentColorParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/documentColor";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentationParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ Color colorInfo() const { return typedValue<Color>(colorInfoKey); }
+ void setColorInfo(const Color &colorInfo) { insert(colorInfoKey, colorInfo); }
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ bool isValid() const override
+ { return contains(textDocumentKey) && contains(colorInfoKey) && contains(rangeKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentation : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString label() const { return typedValue<QString>(labelKey); }
+ void setLabel(const QString &label) { insert(labelKey, label); }
+
+ std::optional<TextEdit> textEdit() const { return optionalValue<TextEdit>(textEditKey); }
+ void setTextEdit(const TextEdit &textEdit) { insert(textEditKey, textEdit); }
+ void clearTextEdit() { remove(textEditKey); }
+
+ std::optional<QList<TextEdit>> additionalTextEdits() const
+ { return optionalArray<TextEdit>(additionalTextEditsKey); }
+ void setAdditionalTextEdits(const QList<TextEdit> &additionalTextEdits)
+ { insertArray(additionalTextEditsKey, additionalTextEdits); }
+ void clearAdditionalTextEdits() { remove(additionalTextEditsKey); }
+
+ bool isValid() const override { return contains(labelKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ColorPresentationRequest : public Request<
+ QList<ColorPresentation>, std::nullptr_t, ColorPresentationParams>
+{
+public:
+ explicit ColorPresentationRequest(const ColorPresentationParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/colorPresentation";
+};
+
+class DocumentFormattingProperty : public std::variant<bool, double, QString>
+{
+public:
+ DocumentFormattingProperty() = default;
+ explicit DocumentFormattingProperty(const QJsonValue &value);
+ explicit DocumentFormattingProperty(const DocumentFormattingProperty &other)
+ : std::variant<bool, double, QString>(other) {}
+
+ using variant::variant;
+ using variant::operator=;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT FormattingOptions : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ int tabSize() const { return typedValue<int>(tabSizeKey); }
+ void setTabSize(int tabSize) { insert(tabSizeKey, tabSize); }
+
+ bool insertSpace() const { return typedValue<bool>(insertSpaceKey); }
+ void setInsertSpace(bool insertSpace) { insert(insertSpaceKey, insertSpace); }
+
+ std::optional<bool> trimTrailingWhitespace() const
+ { return optionalValue<bool>(trimTrailingWhitespaceKey); }
+ void setTrimTrailingWhitespace(bool trimTrailingWhitespace)
+ { insert(trimTrailingWhitespaceKey, trimTrailingWhitespace); }
+ void clearTrimTrailingWhitespace() { remove(trimTrailingWhitespaceKey); }
+
+ std::optional<bool> insertFinalNewline() const
+ { return optionalValue<bool>(insertFinalNewlineKey); }
+ void setInsertFinalNewline(bool insertFinalNewline)
+ { insert(insertFinalNewlineKey, insertFinalNewline); }
+ void clearInsertFinalNewline() { remove(insertFinalNewlineKey); }
+
+ std::optional<bool> trimFinalNewlines() const
+ { return optionalValue<bool>(trimFinalNewlinesKey); }
+ void setTrimFinalNewlines(bool trimFinalNewlines)
+ { insert(trimFinalNewlinesKey, trimFinalNewlines); }
+ void clearTrimFinalNewlines() { remove(trimFinalNewlinesKey); }
+
+ QHash<QString, DocumentFormattingProperty> properties() const;
+ void setProperty(const std::string_view key, const DocumentFormattingProperty &property);
+ void removeProperty(const std::string_view &key) { remove(key); }
+
+ bool isValid() const override { return contains(insertSpaceKey) && contains(tabSizeKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentFormattingParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); }
+ void setOptions(const FormattingOptions &options) { insert(optionsKey, options); }
+
+ bool isValid() const override { return contains(textDocumentKey) && contains(optionsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentFormattingRequest : public Request<
+ LanguageClientArray<TextEdit>, std::nullptr_t, DocumentFormattingParams>
+{
+public:
+ explicit DocumentFormattingRequest(const DocumentFormattingParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/formatting";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentRangeFormattingParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); }
+ void setOptions(const FormattingOptions &options) { insert(optionsKey, options); }
+
+ bool isValid() const override
+ { return contains(textDocumentKey) && contains(rangeKey) && contains(optionsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentRangeFormattingRequest : public Request<
+ LanguageClientArray<TextEdit>, std::nullptr_t, DocumentRangeFormattingParams>
+{
+public:
+ explicit DocumentRangeFormattingRequest(const DocumentRangeFormattingParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/rangeFormatting";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ Position position() const { return typedValue<Position>(positionKey); }
+ void setPosition(const Position &position) { insert(positionKey, position); }
+
+ QString ch() const { return typedValue<QString>(chKey); }
+ void setCh(const QString &ch) { insert(chKey, ch); }
+
+ FormattingOptions options() const { return typedValue<FormattingOptions>(optionsKey); }
+ void setOptions(const FormattingOptions &options) { insert(optionsKey, options); }
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingRequest : public Request<
+ LanguageClientArray<TextEdit>, std::nullptr_t, DocumentOnTypeFormattingParams>
+{
+public:
+ explicit DocumentOnTypeFormattingRequest(const DocumentOnTypeFormattingParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/onTypeFormatting";
+};
+
+class PlaceHolderResult : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ QString placeHolder() const { return typedValue<QString>(placeHolderKey); }
+ void setPlaceHolder(const QString &placeHolder) { insert(placeHolderKey, placeHolder); }
+
+ bool isValid() const override { return contains(rangeKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT PrepareRenameResult
+ : public std::variant<PlaceHolderResult, Range, std::nullptr_t>
+{
+public:
+ PrepareRenameResult();
+ PrepareRenameResult(const std::variant<PlaceHolderResult, Range, std::nullptr_t> &val);
+ explicit PrepareRenameResult(const PlaceHolderResult &val);
+ explicit PrepareRenameResult(const Range &val);
+ explicit PrepareRenameResult(const QJsonValue &val);
+
+ bool isValid() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT PrepareRenameRequest
+ : public Request<PrepareRenameResult, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit PrepareRenameRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/prepareRename";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT RenameParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ Position position() const { return typedValue<Position>(positionKey); }
+ void setPosition(const Position &position) { insert(positionKey, position); }
+
+ QString newName() const { return typedValue<QString>(newNameKey); }
+ void setNewName(const QString &newName) { insert(newNameKey, newName); }
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT RenameRequest : public Request<
+ WorkspaceEdit, std::nullptr_t, RenameParams>
+{
+public:
+ explicit RenameRequest(const RenameParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/rename";
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/languageserverprotocol_global.h b/src/shared/lsp/languageserverprotocol_global.h
new file mode 100644
index 000000000..7f06f1216
--- /dev/null
+++ b/src/shared/lsp/languageserverprotocol_global.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QtGlobal>
+
+#if defined(LANGUAGESERVERPROTOCOL_LIBRARY)
+# define LANGUAGESERVERPROTOCOL_EXPORT Q_DECL_EXPORT
+#elif defined(LANGUAGESERVERPROTOCOL_STATIC_LIBRARY)
+# define LANGUAGESERVERPROTOCOL_EXPORT
+#else
+# define LANGUAGESERVERPROTOCOL_EXPORT Q_DECL_IMPORT
+#endif
diff --git a/src/shared/lsp/languageserverprotocoltr.h b/src/shared/lsp/languageserverprotocoltr.h
new file mode 100644
index 000000000..a4e2f324c
--- /dev/null
+++ b/src/shared/lsp/languageserverprotocoltr.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QCoreApplication>
+
+namespace lsp {
+
+struct Tr
+{
+ Q_DECLARE_TR_FUNCTIONS(QtC::LanguageServerProtocol)
+};
+
+} // LanguageServerProtocol
diff --git a/src/shared/lsp/link.cpp b/src/shared/lsp/link.cpp
new file mode 100644
index 000000000..e25b828d7
--- /dev/null
+++ b/src/shared/lsp/link.cpp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "link.h"
+
+#include "textutils.h"
+
+namespace lsp::Utils {
+
+/*!
+ Returns the Link to \a filePath.
+ If \a canContainLineNumber is true the line number, and column number components
+ are extracted from the \a filePath's \c path() and the found \a postfix is set.
+
+ The following patterns are supported: \c {filepath.txt:19},
+ \c{filepath.txt:19:12}, \c {filepath.txt+19},
+ \c {filepath.txt+19+12}, and \c {filepath.txt(19)}.
+*/
+
+Link Link::fromString(const QString &filePathWithNumbers, bool canContainLineNumber)
+{
+ Link link;
+ if (!canContainLineNumber) {
+ link.targetFilePath = FilePath::fromUserInput(filePathWithNumbers);
+ } else {
+ int postfixPos = -1;
+ const Text::Position pos = Text::Position::fromFileName(filePathWithNumbers, postfixPos);
+ link.targetFilePath = FilePath::fromUserInput(filePathWithNumbers.left(postfixPos));
+ link.targetLine = pos.line;
+ link.targetColumn = pos.column;
+ }
+ return link;
+}
+
+} // namespace lsp::Utils
diff --git a/src/shared/lsp/link.h b/src/shared/lsp/link.h
new file mode 100644
index 000000000..eaafe280c
--- /dev/null
+++ b/src/shared/lsp/link.h
@@ -0,0 +1,78 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "filepath.h"
+
+#include <QMetaType>
+#include <QMultiHash>
+#include <QString>
+
+#include <functional>
+
+namespace lsp::Utils {
+
+class Link
+{
+public:
+ Link() = default;
+ Link(const FilePath &filePath, int line = 0, int column = 0)
+ : targetFilePath(filePath)
+ , targetLine(line)
+ , targetColumn(column)
+ {}
+
+ static Link fromString(const QString &filePathWithNumbers, bool canContainLineNumber = false);
+
+ bool hasValidTarget() const
+ {
+ if (!targetFilePath.isEmpty())
+ return true;
+ return !targetFilePath.scheme().isEmpty() || !targetFilePath.host().isEmpty();
+ }
+
+ bool hasValidLinkText() const
+ { return linkTextStart != linkTextEnd; }
+
+ bool operator==(const Link &other) const
+ {
+ return hasSameLocation(other)
+ && linkTextStart == other.linkTextStart
+ && linkTextEnd == other.linkTextEnd;
+ }
+ bool operator!=(const Link &other) const { return !(*this == other); }
+
+ bool hasSameLocation(const Link &other) const
+ {
+ return targetFilePath == other.targetFilePath
+ && targetLine == other.targetLine
+ && targetColumn == other.targetColumn;
+ }
+
+ friend size_t qHash(const Link &l, uint seed = 0)
+ { return QT_PREPEND_NAMESPACE(qHash)(l.targetFilePath, seed)
+ | QT_PREPEND_NAMESPACE(qHash(l.targetLine, seed))
+ | QT_PREPEND_NAMESPACE(qHash(l.targetColumn, seed)); }
+
+ int linkTextStart = -1;
+ int linkTextEnd = -1;
+
+ FilePath targetFilePath;
+ int targetLine = 0;
+ int targetColumn = 0;
+};
+
+using LinkHandler = std::function<void(const Link &)>;
+using Links = QList<Link>;
+
+} // namespace lsp::Utils
+
+namespace std {
+
+template<> struct hash<lsp::Utils::Link>
+{
+ size_t operator()(const lsp::Utils::Link &fn) const { return qHash(fn); }
+};
+
+} // std
diff --git a/src/shared/lsp/lsp.qbs b/src/shared/lsp/lsp.qbs
new file mode 100644
index 000000000..95323d332
--- /dev/null
+++ b/src/shared/lsp/lsp.qbs
@@ -0,0 +1,72 @@
+import qbs.Utilities
+
+QbsStaticLibrary {
+ name: "qtclsp"
+
+ Depends { name: "qbscore"; cpp.link: false }
+
+ Depends {
+ condition: Utilities.versionCompare(Qt.core.version, "6") >= 0
+ name: "Qt.core5compat"
+ }
+
+ cpp.defines: base.filter(function(d) { return d !== "QT_NO_CAST_FROM_ASCII"; })
+ .concat("LANGUAGESERVERPROTOCOL_STATIC_LIBRARY")
+
+ files: [
+ "algorithm.h",
+ "basemessage.cpp",
+ "basemessage.h",
+ "callhierarchy.cpp",
+ "callhierarchy.h",
+ "client.cpp",
+ "client.h",
+ "clientcapabilities.cpp",
+ "clientcapabilities.h",
+ "completion.cpp",
+ "completion.h",
+ "diagnostics.cpp",
+ "diagnostics.h",
+ "filepath.h",
+ "initializemessages.cpp",
+ "initializemessages.h",
+ "jsonkeys.h",
+ "jsonobject.cpp",
+ "jsonobject.h",
+ "jsonrpcmessages.cpp",
+ "jsonrpcmessages.h",
+ "languagefeatures.cpp",
+ "languagefeatures.h",
+ "languageserverprotocol_global.h",
+ "languageserverprotocoltr.h",
+ "link.cpp",
+ "link.h",
+ "lsptypes.cpp",
+ "lsptypes.h",
+ "lsputils.cpp",
+ "lsputils.h",
+ "messages.cpp",
+ "messages.h",
+ "predicates.h",
+ "progresssupport.cpp",
+ "progresssupport.h",
+ "semantictokens.cpp",
+ "semantictokens.h",
+ "servercapabilities.cpp",
+ "servercapabilities.h",
+ "shutdownmessages.cpp",
+ "shutdownmessages.h",
+ "textsynchronization.cpp",
+ "textsynchronization.h",
+ "textutils.cpp",
+ "textutils.h",
+ "workspace.cpp",
+ "workspace.h",
+ ]
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.defines: "LANGUAGESERVERPROTOCOL_STATIC_LIBRARY"
+ cpp.includePaths: exportingProduct.sourceDirectory + "/.."
+ }
+}
diff --git a/src/shared/lsp/lsptypes.cpp b/src/shared/lsp/lsptypes.cpp
new file mode 100644
index 000000000..751fc4e3d
--- /dev/null
+++ b/src/shared/lsp/lsptypes.cpp
@@ -0,0 +1,364 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "lsptypes.h"
+
+#include "languageserverprotocoltr.h"
+#include "lsputils.h"
+#include "textutils.h"
+
+#include <QFile>
+#include <QHash>
+#include <QJsonArray>
+#include <QMap>
+#include <QRegularExpression>
+#include <QVector>
+
+namespace lsp {
+
+std::optional<DiagnosticSeverity> Diagnostic::severity() const
+{
+ if (auto val = optionalValue<int>(severityKey))
+ return std::make_optional(static_cast<DiagnosticSeverity>(*val));
+ return std::nullopt;
+}
+
+std::optional<Diagnostic::Code> Diagnostic::code() const
+{
+ QJsonValue codeValue = value(codeKey);
+ auto it = find(codeKey);
+ if (codeValue.isUndefined())
+ return std::nullopt;
+ QJsonValue::Type type = it.value().type();
+ if (type != QJsonValue::String && type != QJsonValue::Double)
+ return std::make_optional(Code(QString()));
+ return std::make_optional(codeValue.isDouble() ? Code(codeValue.toInt())
+ : Code(codeValue.toString()));
+}
+
+void Diagnostic::setCode(const Diagnostic::Code &code)
+{
+ insertVariant<int, QString>(codeKey, code);
+}
+
+std::optional<WorkspaceEdit::Changes> WorkspaceEdit::changes() const
+{
+ auto it = find(changesKey);
+ if (it == end())
+ return std::nullopt;
+ const QJsonObject &changesObject = it.value().toObject();
+ Changes changesResult;
+ for (const QString &key : changesObject.keys())
+ changesResult[DocumentUri::fromProtocol(key)] = LanguageClientArray<TextEdit>(changesObject.value(key)).toList();
+ return std::make_optional(changesResult);
+}
+
+void WorkspaceEdit::setChanges(const Changes &changes)
+{
+ QJsonObject changesObject;
+ const auto end = changes.end();
+ for (auto it = changes.begin(); it != end; ++it) {
+ QJsonArray edits;
+ for (const TextEdit &edit : it.value())
+ edits.append(QJsonValue(edit));
+ changesObject.insert(QJsonValue(it.key()).toString(), edits);
+ }
+ insert(changesKey, changesObject);
+}
+
+WorkSpaceFolder::WorkSpaceFolder(const DocumentUri &uri, const QString &name)
+{
+ setUri(uri);
+ setName(name);
+}
+
+MarkupOrString::MarkupOrString(const std::variant<QString, MarkupContent> &val)
+ : std::variant<QString, MarkupContent>(val)
+{ }
+
+MarkupOrString::MarkupOrString(const QString &val)
+ : std::variant<QString, MarkupContent>(val)
+{ }
+
+MarkupOrString::MarkupOrString(const MarkupContent &val)
+ : std::variant<QString, MarkupContent>(val)
+{ }
+
+MarkupOrString::MarkupOrString(const QJsonValue &val)
+{
+ if (val.isString()) {
+ emplace<QString>(val.toString());
+ } else {
+ MarkupContent markupContent(val.toObject());
+ if (markupContent.isValid())
+ emplace<MarkupContent>(MarkupContent(val.toObject()));
+ }
+}
+
+QJsonValue MarkupOrString::toJson() const
+{
+ if (std::holds_alternative<QString>(*this))
+ return std::get<QString>(*this);
+ if (std::holds_alternative<MarkupContent>(*this))
+ return QJsonValue(std::get<MarkupContent>(*this));
+ return {};
+}
+
+QMap<QString, QString> languageIds()
+{
+ static const QMap<QString, QString> languages({
+ {"Windows Bat","bat" },
+ {"BibTeX","bibtex" },
+ {"Clojure","clojure" },
+ {"Coffeescript","coffeescript" },
+ {"C","c" },
+ {"C++","cpp" },
+ {"C#","csharp" },
+ {"CSS","css" },
+ {"Diff","diff" },
+ {"Dockerfile","dockerfile" },
+ {"F#","fsharp" },
+ {"Git commit","git-commit" },
+ {"Git rebase","git-rebase" },
+ {"Go","go" },
+ {"Groovy","groovy" },
+ {"Handlebars","handlebars" },
+ {"HTML","html" },
+ {"Ini","ini" },
+ {"Java","java" },
+ {"JavaScript","javascript" },
+ {"JSON","json" },
+ {"LaTeX","latex" },
+ {"Less","less" },
+ {"Lua","lua" },
+ {"Makefile","makefile" },
+ {"Markdown","markdown" },
+ {"Objective-C","objective-c" },
+ {"Objective-C++","objective-cpp" },
+ {"Perl6","perl6" },
+ {"Perl","perl" },
+ {"PHP","php" },
+ {"Powershell","powershell" },
+ {"Pug","jade" },
+ {"Python","python" },
+ {"R","r" },
+ {"Razor (cshtml)","razor" },
+ {"Ruby","ruby" },
+ {"Rust","rust" },
+ {"Scss (syntax using curly brackets)","scss"},
+ {"Sass (indented syntax)","sass" },
+ {"ShaderLab","shaderlab" },
+ {"Shell Script (Bash)","shellscript" },
+ {"SQL","sql" },
+ {"Swift","swift" },
+ {"TypeScript","typescript" },
+ {"TeX","tex" },
+ {"Visual Basic","vb" },
+ {"XML","xml" },
+ {"XSL","xsl" },
+ {"YAML","yaml" }
+ });
+ return languages;
+}
+
+bool TextDocumentItem::isValid() const
+{
+ return contains(uriKey) && contains(languageIdKey) && contains(versionKey) && contains(textKey);
+}
+
+TextDocumentPositionParams::TextDocumentPositionParams()
+ : TextDocumentPositionParams(TextDocumentIdentifier(), Position())
+{
+
+}
+
+TextDocumentPositionParams::TextDocumentPositionParams(
+ const TextDocumentIdentifier &document, const Position &position)
+{
+ setTextDocument(document);
+ setPosition(position);
+}
+
+Position::Position(int line, int character)
+{
+ setLine(line);
+ setCharacter(character);
+}
+
+Range::Range(const Position &start, const Position &end)
+{
+ setStart(start);
+ setEnd(end);
+}
+
+bool Range::contains(const Range &other) const
+{
+ if (start() > other.start())
+ return false;
+ if (end() < other.end())
+ return false;
+ return true;
+}
+
+bool Range::overlaps(const Range &range) const
+{
+ return !isLeftOf(range) && !range.isLeftOf(*this);
+}
+
+QString expressionForGlob(QString globPattern)
+{
+ const QString anySubDir("qtc_anysubdir_id");
+ globPattern.replace("**/", anySubDir);
+ QString regexp = QRegularExpression::wildcardToRegularExpression(globPattern);
+ regexp.replace(anySubDir,"(.*[/\\\\])*");
+ regexp.replace("\\{", "(");
+ regexp.replace("\\}", ")");
+ regexp.replace(",", "|");
+ return regexp;
+}
+
+bool DocumentFilter::applies(const Utils::FilePath &fileName) const
+{
+ if (std::optional<QString> _pattern = pattern()) {
+ QRegularExpression::PatternOption option = QRegularExpression::NoPatternOption;
+ if (fileName.caseSensitivity() == Qt::CaseInsensitive)
+ option = QRegularExpression::CaseInsensitiveOption;
+ const QRegularExpression regexp(expressionForGlob(*_pattern), option);
+ if (regexp.isValid() && regexp.match(fileName.toString()).hasMatch())
+ return true;
+ }
+ // return false when any of the filter didn't match but return true when no filter was defined
+ return !contains(schemeKey) && !contains(languageKey) && !contains(patternKey);
+}
+
+Utils::Link Location::toLink(const DocumentUri::PathMapper &mapToHostPath) const
+{
+ if (!isValid())
+ return Utils::Link();
+
+ return Utils::Link(uri().toFilePath(mapToHostPath),
+ range().start().line() + 1,
+ range().start().character());
+}
+
+// Ensure %xx like %20 are really decoded using fromPercentEncoding
+// Else, a path with spaces would keep its %20 which would cause failure
+// to open the file by the text editor. This is the cases with compilers in
+// C:\Programs Files on Windows.
+DocumentUri::DocumentUri(const QString &other)
+ : QUrl(QUrl::fromPercentEncoding(other.toUtf8()))
+{ }
+
+DocumentUri::DocumentUri(const Utils::FilePath &other, const PathMapper &mapToServerPath)
+ : QUrl(QUrl::fromLocalFile(mapToServerPath(other).path()))
+{ }
+
+Utils::FilePath DocumentUri::toFilePath(const PathMapper &mapToHostPath) const
+{
+ if (isLocalFile()) {
+ const Utils::FilePath serverPath = Utils::FilePath::fromUserInput(toLocalFile());
+ QBS_ASSERT(mapToHostPath, return serverPath);
+ return mapToHostPath(serverPath);
+ }
+ return {};
+}
+
+DocumentUri DocumentUri::fromProtocol(const QString &uri)
+{
+ return DocumentUri(uri);
+}
+
+DocumentUri DocumentUri::fromFilePath(const Utils::FilePath &file, const PathMapper &mapToServerPath)
+{
+ return DocumentUri(file, mapToServerPath);
+}
+
+MarkupKind::MarkupKind(const QJsonValue &value)
+{
+ m_value = value.toString() == "markdown" ? markdown : plaintext;
+}
+
+MarkupKind::operator QJsonValue() const
+{
+ switch (m_value) {
+ case MarkupKind::markdown:
+ return "markdown";
+ case MarkupKind::plaintext:
+ return "plaintext";
+ }
+ return {};
+}
+
+DocumentChange::DocumentChange(const QJsonValue &value)
+{
+ const QString kind = value["kind"].toString();
+ if (kind == "create")
+ emplace<CreateFileOperation>(value);
+ else if (kind == "rename")
+ emplace<RenameFileOperation>(value);
+ else if (kind == "delete")
+ emplace<DeleteFileOperation>(value);
+ else
+ emplace<TextDocumentEdit>(value);
+}
+
+using DocumentChangeBase = std::variant<TextDocumentEdit, CreateFileOperation, RenameFileOperation, DeleteFileOperation>;
+
+bool DocumentChange::isValid() const
+{
+ return std::visit([](const auto &v) { return v.isValid(); }, DocumentChangeBase(*this));
+}
+
+DocumentChange::operator const QJsonValue () const
+{
+ return std::visit([](const auto &v) { return QJsonValue(v); }, DocumentChangeBase(*this));
+}
+
+CreateFileOperation::CreateFileOperation()
+{
+ insert(kindKey, "create");
+}
+
+QString CreateFileOperation::message(const DocumentUri::PathMapper &mapToHostPath) const
+{
+ return Tr::tr("Create %1").arg(uri().toFilePath(mapToHostPath).toUserOutput());
+}
+
+bool CreateFileOperation::isValid() const
+{
+ return contains(uriKey) && value(kindKey) == "create";
+}
+
+RenameFileOperation::RenameFileOperation()
+{
+ insert(kindKey, "rename");
+}
+
+QString RenameFileOperation::message(const DocumentUri::PathMapper &mapToHostPath) const
+{
+ return Tr::tr("Rename %1 to %2")
+ .arg(oldUri().toFilePath(mapToHostPath).toUserOutput(),
+ newUri().toFilePath(mapToHostPath).toUserOutput());
+}
+
+bool RenameFileOperation::isValid() const
+{
+ return contains(oldUriKey) && contains(newUriKey) && value(kindKey) == "rename";
+}
+
+DeleteFileOperation::DeleteFileOperation()
+{
+ insert(kindKey, "delete");
+}
+
+QString DeleteFileOperation::message(const DocumentUri::PathMapper &mapToHostPath) const
+{
+ return Tr::tr("Delete %1").arg(uri().toFilePath(mapToHostPath).toUserOutput());
+}
+
+bool DeleteFileOperation::isValid() const
+{
+ return contains(uriKey) && value(kindKey) == "delete";
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/lsptypes.h b/src/shared/lsp/lsptypes.h
new file mode 100644
index 000000000..52e7b95a9
--- /dev/null
+++ b/src/shared/lsp/lsptypes.h
@@ -0,0 +1,680 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonkeys.h"
+#include "jsonobject.h"
+#include "lsputils.h"
+#include "filepath.h"
+#include "link.h"
+
+#include <QJsonObject>
+#include <QUrl>
+#include <QList>
+
+#include <optional>
+#include <variant>
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentUri : public QUrl
+{
+public:
+ DocumentUri() = default;
+ using PathMapper = std::function<Utils::FilePath(const Utils::FilePath &)>;
+ Utils::FilePath toFilePath(const PathMapper &mapToHostPath) const;
+
+ static DocumentUri fromProtocol(const QString &uri);
+ static DocumentUri fromFilePath(const Utils::FilePath &file, const PathMapper &mapToServerPath);
+
+ operator QJsonValue() const { return QJsonValue(toString()); }
+
+private:
+ DocumentUri(const QString &other);
+ DocumentUri(const Utils::FilePath &other, const PathMapper &mapToServerPath);
+
+ friend class LanguageClientValue<QString>;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT Position : public JsonObject
+{
+public:
+ Position() = default;
+ Position(int line, int character);
+ using JsonObject::JsonObject;
+
+ // Line position in a document (zero-based).
+ int line() const { return typedValue<int>(lineKey); }
+ void setLine(int line) { insert(lineKey, line); }
+
+ /*
+ * Character offset on a line in a document (zero-based). Assuming that the line is
+ * represented as a string, the `character` value represents the gap between the
+ * `character` and `character + 1`.
+ *
+ * If the character value is greater than the line length it defaults back to the
+ * line length.
+ */
+ int character() const { return typedValue<int>(characterKey); }
+ void setCharacter(int character) { insert(characterKey, character); }
+
+ bool isValid() const override
+ { return contains(lineKey) && contains(characterKey); }
+};
+
+inline bool operator<(const Position &first, const Position &second)
+{
+ return first.line() < second.line()
+ || (first.line() == second.line() && first.character() < second.character());
+}
+
+inline bool operator>(const Position &first, const Position &second)
+{
+ return second < first;
+}
+
+inline bool operator>=(const Position &first, const Position &second)
+{
+ return !(first < second);
+}
+
+inline bool operator<=(const Position &first, const Position &second)
+{
+ return !(first > second);
+}
+
+class LANGUAGESERVERPROTOCOL_EXPORT Range : public JsonObject
+{
+public:
+ Range() = default;
+ Range(const Position &start, const Position &end);
+ using JsonObject::JsonObject;
+
+ // The range's start position.
+ Position start() const { return typedValue<Position>(startKey); }
+ void setStart(const Position &start) { insert(startKey, start); }
+
+ // The range's end position.
+ Position end() const { return typedValue<Position>(endKey); }
+ void setEnd(const Position &end) { insert(endKey, end); }
+
+ bool isEmpty() const { return start() == end(); }
+ bool contains(const Position &pos) const { return start() <= pos && pos <= end(); }
+ bool contains(const Range &other) const;
+ bool overlaps(const Range &range) const;
+ bool isLeftOf(const Range &other) const
+ { return isEmpty() || other.isEmpty() ? end() < other.start() : end() <= other.start(); }
+
+ bool isValid() const override
+ { return JsonObject::contains(startKey) && JsonObject::contains(endKey); }
+};
+
+inline bool operator==(const Range &r1, const Range &r2)
+{
+ return r1.contains(r2) && r2.contains(r1);
+}
+inline bool operator!=(const Range &r1, const Range &r2) { return !(r1 == r2); }
+
+class LANGUAGESERVERPROTOCOL_EXPORT Location : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ Utils::Link toLink(const DocumentUri::PathMapper &mapToHostPath) const;
+
+ bool isValid() const override { return contains(uriKey) && contains(rangeKey); }
+};
+
+enum class DiagnosticSeverity
+{
+ Error = 1,
+ Warning = 2,
+ Information = 3,
+ Hint = 4
+
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT Diagnostic : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // The range at which the message applies.
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ // The diagnostic's severity. Can be omitted. If omitted it is up to the
+ // client to interpret diagnostics as error, warning, info or hint.
+ std::optional<DiagnosticSeverity> severity() const;
+ void setSeverity(const DiagnosticSeverity &severity)
+ { insert(severityKey, static_cast<int>(severity)); }
+ void clearSeverity() { remove(severityKey); }
+
+ // The diagnostic's code, which might appear in the user interface.
+ using Code = std::variant<int, QString>;
+ std::optional<Code> code() const;
+ void setCode(const Code &code);
+ void clearCode() { remove(codeKey); }
+
+ // A human-readable string describing the source of this
+ // diagnostic, e.g. 'typescript' or 'super lint'.
+ std::optional<QString> source() const
+ { return optionalValue<QString>(sourceKey); }
+ void setSource(const QString &source) { insert(sourceKey, source); }
+ void clearSource() { remove(sourceKey); }
+
+ // The diagnostic's message.
+ QString message() const
+ { return typedValue<QString>(messageKey); }
+ void setMessage(const QString &message) { insert(messageKey, message); }
+
+ bool isValid() const override { return contains(rangeKey) && contains(messageKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT Command : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // Title of the command, like `save`.
+ QString title() const { return typedValue<QString>(titleKey); }
+ void setTitle(const QString &title) { insert(titleKey, title); }
+ void clearTitle() { remove(titleKey); }
+
+ // The identifier of the actual command handler.
+ QString command() const { return typedValue<QString>(commandKey); }
+ void setCommand(const QString &command) { insert(commandKey, command); }
+ void clearCommand() { remove(commandKey); }
+
+ // Arguments that the command handler should be invoked with.
+ std::optional<QJsonArray> arguments() const { return typedValue<QJsonArray>(argumentsKey); }
+ void setArguments(const QJsonArray &arguments) { insert(argumentsKey, arguments); }
+ void clearArguments() { remove(argumentsKey); }
+
+ bool isValid() const override { return contains(titleKey) && contains(commandKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextEdit : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // The range of the text document to be manipulated. To insert
+ // text into a document create a range where start === end.
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ // The string to be inserted. For delete operations use an empty string.
+ QString newText() const { return typedValue<QString>(newTextKey); }
+ void setNewText(const QString &text) { insert(newTextKey, text); }
+
+ bool isValid() const override
+ { return contains(rangeKey) && contains(newTextKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentIdentifier : public JsonObject
+{
+public:
+ TextDocumentIdentifier() : TextDocumentIdentifier(DocumentUri()) {}
+ explicit TextDocumentIdentifier(const DocumentUri &uri) { setUri(uri); }
+ using JsonObject::JsonObject;
+
+ // The text document's URI.
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ bool isValid() const override { return contains(uriKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT VersionedTextDocumentIdentifier : public TextDocumentIdentifier
+{
+public:
+ using TextDocumentIdentifier::TextDocumentIdentifier;
+
+ /*
+ * The version number of this document. If a versioned text document identifier
+ * is sent from the server to the client and the file is not open in the editor
+ * (the server has not received an open notification before) the server can send
+ * `null` to indicate that the version is known and the content on disk is the
+ * truth (as speced with document content ownership)
+ */
+ LanguageClientValue<int> version() const { return clientValue<int>(versionKey); }
+ void setVersion(LanguageClientValue<int> version) { insert(versionKey, version); }
+
+ bool isValid() const override
+ {
+ return TextDocumentIdentifier::isValid() && contains(versionKey);
+ }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentEdit : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // The text document to change.
+ VersionedTextDocumentIdentifier textDocument() const
+ { return typedValue<VersionedTextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const VersionedTextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ // The edits to be applied.
+ QList<TextEdit> edits() const { return array<TextEdit>(editsKey); }
+ void setEdits(const QList<TextEdit> edits) { insertArray(editsKey, edits); }
+
+ bool isValid() const override { return contains(textDocumentKey) && contains(editsKey); }
+};
+
+class CreateFileOptions : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ std::optional<bool> overwrite() const { return optionalValue<bool>(overwriteKey); }
+ void setOverwrite(bool overwrite) { insert(overwriteKey, overwrite); }
+ void clearOverwrite() { remove(overwriteKey); }
+
+ std::optional<bool> ignoreIfExists() const { return optionalValue<bool>(ignoreIfExistsKey); }
+ void setIgnoreIfExists(bool ignoreIfExists) { insert(ignoreIfExistsKey, ignoreIfExists); }
+ void clearIgnoreIfExists() { remove(ignoreIfExistsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CreateFileOperation : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+ CreateFileOperation();
+
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ std::optional<CreateFileOptions> options() const
+ { return optionalValue<CreateFileOptions>(optionsKey); }
+ void setOptions(const CreateFileOptions &options) { insert(optionsKey, options); }
+ void clearOptions() { remove(optionsKey); }
+
+ QString message(const DocumentUri::PathMapper &mapToHostPath) const;
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT RenameFileOperation : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+ RenameFileOperation();
+
+ DocumentUri oldUri() const { return DocumentUri::fromProtocol(typedValue<QString>(oldUriKey)); }
+ void setOldUri(const DocumentUri &oldUri) { insert(oldUriKey, oldUri); }
+
+ DocumentUri newUri() const { return DocumentUri::fromProtocol(typedValue<QString>(newUriKey)); }
+ void setNewUri(const DocumentUri &newUri) { insert(newUriKey, newUri); }
+
+ std::optional<CreateFileOptions> options() const
+ { return optionalValue<CreateFileOptions>(optionsKey); }
+ void setOptions(const CreateFileOptions &options) { insert(optionsKey, options); }
+ void clearOptions() { remove(optionsKey); }
+
+ QString message(const DocumentUri::PathMapper &mapToHostPath) const;
+
+ bool isValid() const override;
+};
+
+class DeleteFileOptions : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ std::optional<bool> recursive() const { return optionalValue<bool>(recursiveKey); }
+ void setRecursive(bool recursive) { insert(recursiveKey, recursive); }
+ void clearRecursive() { remove(recursiveKey); }
+
+ std::optional<bool> ignoreIfNotExists() const { return optionalValue<bool>(ignoreIfNotExistsKey); }
+ void setIgnoreIfNotExists(bool ignoreIfNotExists) { insert(ignoreIfNotExistsKey, ignoreIfNotExists); }
+ void clearIgnoreIfNotExists() { remove(ignoreIfNotExistsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DeleteFileOperation : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+ DeleteFileOperation();
+
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ std::optional<DeleteFileOptions> options() const
+ { return optionalValue<DeleteFileOptions>(optionsKey); }
+ void setOptions(const DeleteFileOptions &options) { insert(optionsKey, options); }
+ void clearOptions() { remove(optionsKey); }
+
+ QString message(const DocumentUri::PathMapper &mapToHostPath) const;
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentChange
+ : public std::variant<TextDocumentEdit, CreateFileOperation, RenameFileOperation, DeleteFileOperation>
+{
+public:
+ using variant::variant;
+ DocumentChange(const QJsonValue &value);
+
+ bool isValid() const;
+
+ operator const QJsonValue() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceEdit : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // Holds changes to existing resources.
+ using Changes = QMap<DocumentUri, QList<TextEdit>>;
+ std::optional<Changes> changes() const;
+ void setChanges(const Changes &changes);
+
+ /*
+ * Depending on the client capability
+ * `workspace.workspaceEdit.resourceOperations` document changes are either
+ * an array of `TextDocumentEdit`s to express changes to n different text
+ * documents where each text document edit addresses a specific version of
+ * a text document. Or it can contain above `TextDocumentEdit`s mixed with
+ * create, rename and delete file / folder operations.
+ *
+ * Whether a client supports versioned document edits is expressed via
+ * `workspace.workspaceEdit.documentChanges` client capability.
+ *
+ * If a client neither supports `documentChanges` nor
+ * `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
+ * using the `changes` property are supported.
+ */
+ std::optional<QList<DocumentChange>> documentChanges() const
+ { return optionalArray<DocumentChange>(documentChangesKey); }
+ void setDocumentChanges(const QList<DocumentChange> &changes)
+ { insertArray(documentChangesKey, changes); }
+};
+
+LANGUAGESERVERPROTOCOL_EXPORT QMap<QString, QString> languageIds();
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentItem : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // The text document's URI.
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ // The text document's language identifier.
+ QString languageId() const { return typedValue<QString>(languageIdKey); }
+ void setLanguageId(const QString &id) { insert(languageIdKey, id); }
+
+ // The version number of this document (it will increase after each change, including undo/redo
+ int version() const { return typedValue<int>(versionKey); }
+ void setVersion(int version) { insert(versionKey, version); }
+
+ // The content of the opened text document.
+ QString text() const { return typedValue<QString>(textKey); }
+ void setText(const QString &text) { insert(textKey, text); }
+
+ bool isValid() const override;
+
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentPositionParams : public JsonObject
+{
+public:
+ TextDocumentPositionParams();
+ TextDocumentPositionParams(const TextDocumentIdentifier &document, const Position &position);
+ using JsonObject::JsonObject;
+
+ // The text document.
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); }
+
+ // The position inside the text document.
+ Position position() const { return typedValue<Position>(positionKey); }
+ void setPosition(const Position &position) { insert(positionKey, position); }
+
+ bool isValid() const override { return contains(textDocumentKey) && contains(positionKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentFilter : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // A language id, like `typescript`.
+ std::optional<QString> language() const { return optionalValue<QString>(languageKey); }
+ void setLanguage(const QString &language) { insert(languageKey, language); }
+ void clearLanguage() { remove(languageKey); }
+
+ // A Uri [scheme](#Uri.scheme), like `file` or `untitled`.
+ std::optional<QString> scheme() const { return optionalValue<QString>(schemeKey); }
+ void setScheme(const QString &scheme) { insert(schemeKey, scheme); }
+ void clearScheme() { remove(schemeKey); }
+
+ /**
+ * A glob pattern, like `*.{ts,js}`.
+ *
+ * Glob patterns can have the following syntax:
+ * - `*` to match one or more characters in a path segment
+ * - `?` to match on one character in a path segment
+ * - `**` to match any number of path segments, including none
+ * - `{}` to group sub patterns into an OR expression. (e.g. `*.{ts,js}`
+ * matches all TypeScript and JavaScript files)
+ * - `[]` to declare a range of characters to match in a path segment
+ * (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
+ * - `[!...]` to negate a range of characters to match in a path segment
+ * (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but
+ * not `example.0`)
+ */
+ std::optional<QString> pattern() const { return optionalValue<QString>(patternKey); }
+ void setPattern(const QString &pattern) { insert(patternKey, pattern); }
+ void clearPattern() { remove(patternKey); }
+
+ bool applies(const Utils::FilePath &fileName) const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT MarkupKind
+{
+public:
+ enum Value { plaintext, markdown };
+ MarkupKind() = default;
+ MarkupKind(const Value value)
+ : m_value(value)
+ {}
+ explicit MarkupKind(const QJsonValue &value);
+
+ operator QJsonValue() const;
+ Value value() const { return m_value; }
+
+ bool operator==(const Value &value) const { return m_value == value; }
+
+ bool isValid() const { return true; }
+private:
+ Value m_value = plaintext;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT MarkupContent : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // The type of the Markup
+ MarkupKind kind() const { return MarkupKind(value(kindKey)); }
+ void setKind(MarkupKind kind) { insert(kindKey, kind); }
+ Qt::TextFormat textFormat() const
+ { return kind() == MarkupKind::markdown ? Qt::MarkdownText : Qt::PlainText; }
+
+ // The content itself
+ QString content() const { return typedValue<QString>(contentKey); }
+ void setContent(const QString &content) { insert(contentKey, content); }
+
+ bool isValid() const override
+ { return contains(kindKey) && contains(contentKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT MarkupOrString : public std::variant<QString, MarkupContent>
+{
+public:
+ MarkupOrString() = default;
+ explicit MarkupOrString(const std::variant<QString, MarkupContent> &val);
+ explicit MarkupOrString(const QString &val);
+ explicit MarkupOrString(const MarkupContent &val);
+ MarkupOrString(const QJsonValue &val);
+
+ bool isValid() const { return true; }
+
+ QJsonValue toJson() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkSpaceFolder : public JsonObject
+{
+public:
+ WorkSpaceFolder() = default;
+ WorkSpaceFolder(const DocumentUri &uri, const QString &name);
+ using JsonObject::JsonObject;
+
+ // The associated URI for this workspace folder.
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ // The name of the workspace folder. Defaults to the uri's basename.
+ QString name() const { return typedValue<QString>(nameKey); }
+ void setName(const QString &name) { insert(nameKey, name); }
+
+ bool isValid() const override
+ { return contains(uriKey) && contains(nameKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SymbolInformation : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString name() const { return typedValue<QString>(nameKey); }
+ void setName(const QString &name) { insert(nameKey, name); }
+
+ int kind() const { return typedValue<int>(kindKey); }
+ void setKind(int kind) { insert(kindKey, kind); }
+
+ std::optional<bool> deprecated() const { return optionalValue<bool>(deprecatedKey); }
+ void setDeprecated(bool deprecated) { insert(deprecatedKey, deprecated); }
+ void clearDeprecated() { remove(deprecatedKey); }
+
+ Location location() const { return typedValue<Location>(locationKey); }
+ void setLocation(const Location &location) { insert(locationKey, location); }
+
+ std::optional<QString> containerName() const
+ { return optionalValue<QString>(containerNameKey); }
+ void setContainerName(const QString &containerName) { insert(containerNameKey, containerName); }
+ void clearContainerName() { remove(containerNameKey); }
+
+ bool isValid() const override
+ { return contains(nameKey) && contains(kindKey) && contains(locationKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DocumentSymbol : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString name() const { return typedValue<QString>(nameKey); }
+ void setName(const QString &name) { insert(nameKey, name); }
+
+ std::optional<QString> detail() const { return optionalValue<QString>(detailKey); }
+ void setDetail(const QString &detail) { insert(detailKey, detail); }
+ void clearDetail() { remove(detailKey); }
+
+ int kind() const { return typedValue<int>(kindKey); }
+ void setKind(int kind) { insert(kindKey, kind); }
+
+ std::optional<bool> deprecated() const { return optionalValue<bool>(deprecatedKey); }
+ void setDeprecated(bool deprecated) { insert(deprecatedKey, deprecated); }
+ void clearDeprecated() { remove(deprecatedKey); }
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(Range range) { insert(rangeKey, range); }
+
+ Range selectionRange() const { return typedValue<Range>(selectionRangeKey); }
+ void setSelectionRange(Range selectionRange) { insert(selectionRangeKey, selectionRange); }
+
+ std::optional<QList<DocumentSymbol>> children() const
+ { return optionalArray<DocumentSymbol>(childrenKey); }
+ void setChildren(QList<DocumentSymbol> children) { insertArray(childrenKey, children); }
+ void clearChildren() { remove(childrenKey); }
+};
+
+enum class SymbolKind {
+ File = 1,
+ FirstSymbolKind = File,
+ Module = 2,
+ Namespace = 3,
+ Package = 4,
+ Class = 5,
+ Method = 6,
+ Property = 7,
+ Field = 8,
+ Constructor = 9,
+ Enum = 10,
+ Interface = 11,
+ Function = 12,
+ Variable = 13,
+ Constant = 14,
+ String = 15,
+ Number = 16,
+ Boolean = 17,
+ Array = 18,
+ Object = 19,
+ Key = 20,
+ Null = 21,
+ EnumMember = 22,
+ Struct = 23,
+ Event = 24,
+ Operator = 25,
+ TypeParameter = 26,
+ LastSymbolKind = TypeParameter,
+};
+
+namespace CompletionItemKind {
+enum Kind {
+ Text = 1,
+ Method = 2,
+ Function = 3,
+ Constructor = 4,
+ Field = 5,
+ Variable = 6,
+ Class = 7,
+ Interface = 8,
+ Module = 9,
+ Property = 10,
+ Unit = 11,
+ Value = 12,
+ Enum = 13,
+ Keyword = 14,
+ Snippet = 15,
+ Color = 16,
+ File = 17,
+ Reference = 18,
+ Folder = 19,
+ EnumMember = 20,
+ Constant = 21,
+ Struct = 22,
+ Event = 23,
+ Operator = 24,
+ TypeParameter = 25
+};
+} // namespace CompletionItemKind
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/lsputils.cpp b/src/shared/lsp/lsputils.cpp
new file mode 100644
index 000000000..f26a8a21c
--- /dev/null
+++ b/src/shared/lsp/lsputils.cpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "lsputils.h"
+
+#include <QHash>
+#include <QLoggingCategory>
+#include <QVector>
+
+namespace lsp {
+
+Q_LOGGING_CATEGORY(conversionLog, "qtc.languageserverprotocol.conversion", QtWarningMsg)
+
+template<>
+QString fromJsonValue<QString>(const QJsonValue &value)
+{
+ if (conversionLog().isDebugEnabled() && !value.isString())
+ qCDebug(conversionLog) << "Expected String in json value but got: " << value;
+ return value.toString();
+}
+
+template<>
+int fromJsonValue<int>(const QJsonValue &value)
+{
+ if (conversionLog().isDebugEnabled() && !value.isDouble())
+ qCDebug(conversionLog) << "Expected double in json value but got: " << value;
+ return value.toInt();
+}
+
+template<>
+double fromJsonValue<double>(const QJsonValue &value)
+{
+ if (conversionLog().isDebugEnabled() && !value.isDouble())
+ qCDebug(conversionLog) << "Expected double in json value but got: " << value;
+ return value.toDouble();
+}
+
+template<>
+bool fromJsonValue<bool>(const QJsonValue &value)
+{
+ if (conversionLog().isDebugEnabled() && !value.isBool())
+ qCDebug(conversionLog) << "Expected bool in json value but got: " << value;
+ return value.toBool();
+}
+
+template<>
+QJsonArray fromJsonValue<QJsonArray>(const QJsonValue &value)
+{
+ if (conversionLog().isDebugEnabled() && !value.isArray())
+ qCDebug(conversionLog) << "Expected Array in json value but got: " << value;
+ return value.toArray();
+}
+
+template<>
+QJsonObject fromJsonValue<QJsonObject>(const QJsonValue &value)
+{
+ if (conversionLog().isDebugEnabled() && !value.isObject())
+ qCDebug(conversionLog) << "Expected Object in json value but got: " << value;
+ return value.toObject();
+}
+
+template<>
+QJsonValue fromJsonValue<QJsonValue>(const QJsonValue &value)
+{
+ return value;
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/lsputils.h b/src/shared/lsp/lsputils.h
new file mode 100644
index 000000000..eb7a18342
--- /dev/null
+++ b/src/shared/lsp/lsputils.h
@@ -0,0 +1,160 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "languageserverprotocol_global.h"
+
+#include <tools/qbsassert.h>
+
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QLoggingCategory>
+
+#include <variant>
+
+namespace lsp {
+
+LANGUAGESERVERPROTOCOL_EXPORT Q_DECLARE_LOGGING_CATEGORY(conversionLog)
+
+template <typename T>
+T fromJsonValue(const QJsonValue &value)
+{
+ if (conversionLog().isDebugEnabled() && !value.isObject())
+ qCDebug(conversionLog) << "Expected Object in json value but got: " << value;
+ T result(value.toObject());
+ if (conversionLog().isDebugEnabled() && !result.isValid())
+ qCDebug(conversionLog) << typeid(result).name() << " is not valid: " << result;
+ return result;
+}
+
+template<>
+LANGUAGESERVERPROTOCOL_EXPORT QString fromJsonValue<QString>(const QJsonValue &value);
+
+template<>
+LANGUAGESERVERPROTOCOL_EXPORT int fromJsonValue<int>(const QJsonValue &value);
+
+template<>
+LANGUAGESERVERPROTOCOL_EXPORT double fromJsonValue<double>(const QJsonValue &value);
+
+template<>
+LANGUAGESERVERPROTOCOL_EXPORT bool fromJsonValue<bool>(const QJsonValue &value);
+
+template<>
+LANGUAGESERVERPROTOCOL_EXPORT QJsonObject fromJsonValue<QJsonObject>(const QJsonValue &value);
+
+template<>
+LANGUAGESERVERPROTOCOL_EXPORT QJsonArray fromJsonValue<QJsonArray>(const QJsonValue &value);
+
+template<>
+LANGUAGESERVERPROTOCOL_EXPORT QJsonValue fromJsonValue<QJsonValue>(const QJsonValue &value);
+
+template <typename T>
+class LanguageClientArray : public std::variant<QList<T>, std::nullptr_t>
+{
+public:
+ using std::variant<QList<T>, std::nullptr_t>::variant;
+ using std::variant<QList<T>, std::nullptr_t>::operator=;
+
+ LanguageClientArray() {}
+
+ explicit LanguageClientArray(const QList<T> &list)
+ { *this = list; }
+
+ explicit LanguageClientArray(const QJsonValue &value)
+ {
+ if (value.isArray()) {
+ QList<T> values;
+ values.reserve(value.toArray().count());
+ for (auto arrayValue : value.toArray())
+ values << fromJsonValue<T>(arrayValue);
+ *this = values;
+ } else {
+ *this = nullptr;
+ }
+ }
+
+ QJsonValue toJson() const
+ {
+ if (const auto list = std::get_if<QList<T>>(this)) {
+ QJsonArray array;
+ for (const T &value : *list)
+ array.append(QJsonValue(value));
+ return array;
+ }
+ return QJsonValue();
+ }
+
+ QList<T> toListOrEmpty() const
+ {
+ if (std::holds_alternative<QList<T>>(*this))
+ return std::get<QList<T>>(*this);
+ return {};
+ }
+
+ QList<T> toList() const
+ {
+ QBS_ASSERT(std::holds_alternative<QList<T>>(*this), return {});
+ return std::get<QList<T>>(*this);
+ }
+ bool isNull() const { return std::holds_alternative<std::nullptr_t>(*this); }
+};
+
+template <typename T>
+class LanguageClientValue : public std::variant<T, std::nullptr_t>
+{
+public:
+ using std::variant<T, std::nullptr_t>::operator=;
+
+ LanguageClientValue() : std::variant<T, std::nullptr_t>(nullptr) { }
+ LanguageClientValue(const T &value) : std::variant<T, std::nullptr_t>(value) { }
+ LanguageClientValue(const QJsonValue &value)
+ {
+ if (!QBS_GUARD(!value.isUndefined()) || value.isNull())
+ *this = nullptr;
+ else
+ *this = fromJsonValue<T>(value);
+ }
+
+ operator const QJsonValue() const
+ {
+ if (auto val = std::get_if<T>(this))
+ return QJsonValue(*val);
+ return QJsonValue();
+ }
+
+ T value(const T &defaultValue = T()) const
+ {
+ QBS_ASSERT(std::holds_alternative<T>(*this), return defaultValue);
+ return std::get<T>(*this);
+ }
+
+ template<typename Type>
+ LanguageClientValue<Type> transform()
+ {
+ QBS_ASSERT(!std::holds_alternative<T>(*this), return LanguageClientValue<Type>());
+ return Type(std::get<T>(*this));
+ }
+
+ bool isNull() const { return std::holds_alternative<std::nullptr_t>(*this); }
+};
+
+template <typename T>
+QJsonArray enumArrayToJsonArray(const QList<T> &values)
+{
+ QJsonArray array;
+ for (T value : values)
+ array.append(static_cast<int>(value));
+ return array;
+}
+
+template <typename T>
+QList<T> jsonArrayToList(const QJsonArray &array)
+{
+ QList<T> list;
+ for (const QJsonValue &val : array)
+ list << fromJsonValue<T>(val);
+ return list;
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/messages.cpp b/src/shared/lsp/messages.cpp
new file mode 100644
index 000000000..a909fdb82
--- /dev/null
+++ b/src/shared/lsp/messages.cpp
@@ -0,0 +1,45 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "messages.h"
+
+namespace lsp {
+
+constexpr const char ShowMessageNotification::methodName[];
+constexpr const char ShowMessageRequest::methodName[];
+constexpr const char LogMessageNotification::methodName[];
+constexpr const char TelemetryNotification::methodName[];
+
+ShowMessageNotification::ShowMessageNotification(const ShowMessageParams &params)
+ : Notification(methodName, params)
+{ }
+
+ShowMessageRequest::ShowMessageRequest(const ShowMessageRequestParams &params)
+ : Request(methodName, params)
+{ }
+
+LogMessageNotification::LogMessageNotification(const LogMessageParams &params)
+ : Notification(methodName, params)
+{ }
+
+TelemetryNotification::TelemetryNotification(const JsonObject &params)
+ : Notification(methodName, params)
+{ }
+
+static QString messageTypeName(int messageType)
+{
+ switch (messageType) {
+ case Error: return QString("Error");
+ case Warning: return QString("Warning");
+ case Info: return QString("Info");
+ case Log: return QString("Log");
+ }
+ return QString("");
+}
+
+QString ShowMessageParams::toString() const
+{
+ return messageTypeName(type()) + ": " + message();
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/messages.h b/src/shared/lsp/messages.h
new file mode 100644
index 000000000..8c7d05c6d
--- /dev/null
+++ b/src/shared/lsp/messages.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+
+namespace lsp {
+
+enum MessageType {
+ Error = 1,
+ Warning = 2,
+ Info = 3,
+ Log = 4,
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ int type() const { return typedValue<int>(typeKey); }
+ void setType(int type) { insert(typeKey, type); }
+
+ QString message() const { return typedValue<QString>(messageKey); }
+ void setMessage(QString message) { insert(messageKey, message); }
+
+ QString toString() const;
+
+ bool isValid() const override
+ { return contains(typeKey) && contains(messageKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageNotification : public Notification<ShowMessageParams>
+{
+public:
+ explicit ShowMessageNotification(const ShowMessageParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "window/showMessage";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT MessageActionItem : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString title() const { return typedValue<QString>(titleKey); }
+ void setTitle(QString title) { insert(titleKey, title); }
+
+ bool isValid() const override { return contains(titleKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageRequestParams : public ShowMessageParams
+{
+public:
+ using ShowMessageParams::ShowMessageParams;
+
+ std::optional<QList<MessageActionItem>> actions() const
+ { return optionalArray<MessageActionItem>(actionsKey); }
+ void setActions(const QList<MessageActionItem> &actions) { insertArray(actionsKey, actions); }
+ void clearActions() { remove(actionsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ShowMessageRequest : public Request<
+ LanguageClientValue<MessageActionItem>, std::nullptr_t, ShowMessageRequestParams>
+{
+public:
+ explicit ShowMessageRequest(const ShowMessageRequestParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "window/showMessageRequest";
+};
+
+using LogMessageParams = ShowMessageParams;
+
+class LANGUAGESERVERPROTOCOL_EXPORT LogMessageNotification : public Notification<LogMessageParams>
+{
+public:
+ explicit LogMessageNotification(const LogMessageParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "window/logMessage";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TelemetryNotification : public Notification<JsonObject>
+{
+public:
+ explicit TelemetryNotification(const JsonObject &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "telemetry/event";
+
+ bool parametersAreValid(QString * /*error*/) const override { return params().has_value(); }
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/predicates.h b/src/shared/lsp/predicates.h
new file mode 100644
index 000000000..800a84757
--- /dev/null
+++ b/src/shared/lsp/predicates.h
@@ -0,0 +1,110 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+namespace lsp::Utils
+{
+
+//////////////////
+// find helpers
+//////////////////
+template<typename R, typename S, typename T>
+decltype(auto) equal(R (S::*function)() const, T value)
+{
+ // This should use std::equal_to<> instead of std::equal_to<T>,
+ // but that's not supported everywhere yet, since it is C++14
+ return std::bind<bool>(std::equal_to<T>(), value, std::bind(function, std::placeholders::_1));
+}
+
+template<typename R, typename S, typename T>
+decltype(auto) equal(R S::*member, T value)
+{
+ return std::bind<bool>(std::equal_to<T>(), value, std::bind(member, std::placeholders::_1));
+}
+
+//////////////////
+// comparison predicates
+//////////////////
+template <typename Type>
+auto equalTo(Type &&value)
+{
+ return [value = std::forward<Type>(value)] (const auto &entry)
+ {
+ static_assert(std::is_same<std::decay_t<Type>,
+ std::decay_t<decltype(entry)>>::value,
+ "The container and predicate type of equalTo should be the same to prevent "
+ "unnecessary conversion.");
+ return entry == value;
+ };
+}
+
+template <typename Type>
+auto unequalTo(Type &&value)
+{
+ return [value = std::forward<Type>(value)] (const auto &entry)
+ {
+ static_assert(std::is_same<std::decay_t<Type>,
+ std::decay_t<decltype(entry)>>::value,
+ "The container and predicate type of unequalTo should be the same to prevent "
+ "unnecessary conversion.");
+ return !(entry == value);
+ };
+}
+
+template <typename Type>
+auto lessThan(Type &&value)
+{
+ return [value = std::forward<Type>(value)] (const auto &entry)
+ {
+ static_assert(std::is_same<std::decay_t<Type>,
+ std::decay_t<decltype(entry)>>::value,
+ "The container and predicate type of unequalTo should be the same to prevent "
+ "unnecessary conversion.");
+ return entry < value;
+ };
+}
+
+template <typename Type>
+auto lessEqualThan(Type &&value)
+{
+ return [value = std::forward<Type>(value)] (const auto &entry)
+ {
+ static_assert(std::is_same<std::decay_t<Type>,
+ std::decay_t<decltype(entry)>>::value,
+ "The container and predicate type of lessEqualThan should be the same to "
+ "prevent unnecessary conversion.");
+ return !(value < entry);
+ };
+}
+
+template <typename Type>
+auto greaterThan(Type &&value)
+{
+ return [value = std::forward<Type>(value)] (const auto &entry)
+ {
+ static_assert(std::is_same<std::decay_t<Type>,
+ std::decay_t<decltype(entry)>>::value,
+ "The container and predicate type of greaterThan should be the same to "
+ "prevent unnecessary conversion.");
+ return value < entry;
+ };
+}
+
+template <typename Type>
+auto greaterEqualThan(Type &&value)
+{
+ return [value = std::forward<Type>(value)] (const auto &entry)
+ {
+ static_assert(std::is_same<std::decay_t<Type>,
+ std::decay_t<decltype(entry)>>::value,
+ "The container and predicate type of greaterEqualThan should be the same to "
+ "prevent unnecessary conversion.");
+ return !(entry < value);
+ };
+}
+} // namespace Utils
diff --git a/src/shared/lsp/progresssupport.cpp b/src/shared/lsp/progresssupport.cpp
new file mode 100644
index 000000000..6a554b4f4
--- /dev/null
+++ b/src/shared/lsp/progresssupport.cpp
@@ -0,0 +1,54 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "progresssupport.h"
+
+#include <QUuid>
+
+namespace lsp {
+
+ProgressToken::ProgressToken(const QJsonValue &value)
+{
+ if (!QBS_GUARD(value.isDouble() || value.isString()))
+ emplace<QString>(QUuid::createUuid().toString());
+ else if (value.isDouble())
+ emplace<int>(value.toInt());
+ else
+ emplace<QString>(value.toString());
+}
+
+ProgressToken::operator QJsonValue() const
+{
+ if (std::holds_alternative<QString>(*this))
+ return QJsonValue(std::get<QString>(*this));
+ return QJsonValue(std::get<int>(*this));
+}
+
+ProgressParams::ProgressType ProgressParams::value() const
+{
+ QJsonObject paramsValue = JsonObject::value(valueKey).toObject();
+ if (paramsValue[kindKey] == "begin")
+ return ProgressParams::ProgressType(WorkDoneProgressBegin(paramsValue));
+ if (paramsValue[kindKey] == "report")
+ return ProgressParams::ProgressType(WorkDoneProgressReport(paramsValue));
+ return ProgressParams::ProgressType(WorkDoneProgressEnd(paramsValue));
+}
+
+void ProgressParams::setValue(const ProgressParams::ProgressType &value)
+{
+ insertVariant<WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd>(valueKey, value);
+}
+
+ProgressNotification::ProgressNotification(const ProgressParams &params)
+ : Notification(methodName, params)
+{
+
+}
+
+WorkDoneProgressCreateRequest::WorkDoneProgressCreateRequest(const WorkDoneProgressCreateParams &params)
+ : Request(methodName, params)
+{
+
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/progresssupport.h b/src/shared/lsp/progresssupport.h
new file mode 100644
index 000000000..8ccc34c37
--- /dev/null
+++ b/src/shared/lsp/progresssupport.h
@@ -0,0 +1,152 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+
+#include <QJsonValue>
+
+#include <variant>
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT ProgressToken : public std::variant<int, QString>
+{
+public:
+ using variant::variant;
+ explicit ProgressToken(const QJsonValue &value);
+
+ bool isValid() { return true; }
+ operator QJsonValue() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressReport : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /**
+ * Controls if a cancel button should be shown to allow the user to cancel the
+ * long running operation.
+ * Clients that don't support cancellation can ignore the setting.
+ */
+ std::optional<bool> cancellable() const { return optionalValue<bool>(cancellableKey); }
+ void setCancellable(bool cancellable) { insert(cancellableKey, cancellable); }
+ void clearCancellable() { remove(cancellableKey); }
+
+ /**
+ * Optional, more detailed associated progress message. Contains
+ * complementary information to the `title`.
+ *
+ * Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
+ * If unset, the previous progress message (if any) is still valid.
+ */
+ std::optional<QString> message() const { return optionalValue<QString>(messageKey); }
+ void setMessage(const QString &message) { insert(messageKey, message); }
+ void clearMessage() { remove(messageKey); }
+
+ /**
+ * Optional progress percentage to display (value 100 is considered 100%).
+ * If not provided infinite progress is assumed and clients are allowed
+ * to ignore the `percentage` value in subsequent in report notifications.
+ *
+ * The value should be steadily rising. Clients are free to ignore values
+ * that are not following this rule.
+ */
+
+ // Allthough percentage is defined as an uint by the protocol some server
+ // return a double here. Be less strict and also use a double.
+ // CAUTION: the range is still 0 - 100 and not 0 - 1
+ std::optional<double> percentage() const { return optionalValue<double>(percentageKey); }
+ void setPercentage(double percentage) { insert(percentageKey, percentage); }
+ void clearPercentage() { remove(percentageKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressBegin : public WorkDoneProgressReport
+{
+public:
+ using WorkDoneProgressReport::WorkDoneProgressReport;
+
+ /**
+ * Mandatory title of the progress operation. Used to briefly inform about
+ * the kind of operation being performed.
+ *
+ * Examples: "Indexing" or "Linking dependencies".
+ */
+
+ QString title() const { return typedValue<QString>(titleKey); }
+ void setTitle(const QString &title) { insert(titleKey, title); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressEnd : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /**
+ * Optional, a final message indicating to for example indicate the outcome
+ * of the operation.
+ */
+ std::optional<QString> message() const { return optionalValue<QString>(messageKey); }
+ void setMessage(const QString &message) { insert(messageKey, message); }
+ void clearMessage() { remove(messageKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ProgressParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ ProgressToken token() const { return ProgressToken(JsonObject::value(tokenKey)); }
+ void setToken(const ProgressToken &token) { insert(tokenKey, token); }
+
+ using ProgressType
+ = std::variant<WorkDoneProgressBegin, WorkDoneProgressReport, WorkDoneProgressEnd>;
+ ProgressType value() const;
+ void setValue(const ProgressType &value);
+
+ bool isValid() const override { return contains(tokenKey) && contains(valueKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ProgressNotification : public Notification<ProgressParams>
+{
+public:
+ ProgressNotification(const ProgressParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "$/progress";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ProgressTokenParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // The token to be used to report progress.
+ ProgressToken token() const { return typedValue<ProgressToken>(tokenKey); }
+ void setToken(const ProgressToken &token) { insert(tokenKey, token); }
+};
+
+using WorkDoneProgressCreateParams = ProgressTokenParams;
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressCreateRequest
+ : public Request<std::nullptr_t, std::nullptr_t, WorkDoneProgressCreateParams>
+{
+public:
+ WorkDoneProgressCreateRequest(const WorkDoneProgressCreateParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "window/workDoneProgress/create";
+};
+
+using WorkDoneProgressCancelParams = ProgressTokenParams;
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressCancelRequest
+ : public Request<std::nullptr_t, std::nullptr_t, WorkDoneProgressCancelParams>
+{
+public:
+ using Request::Request;
+ constexpr static const char methodName[] = "window/workDoneProgress/cancel";
+};
+
+
+} // namespace lsp
diff --git a/src/shared/lsp/semantictokens.cpp b/src/shared/lsp/semantictokens.cpp
new file mode 100644
index 000000000..209966dcf
--- /dev/null
+++ b/src/shared/lsp/semantictokens.cpp
@@ -0,0 +1,141 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "semantictokens.h"
+
+namespace lsp {
+
+bool SemanticTokensLegend::isValid() const
+{
+ return contains(tokenTypesKey) && contains(tokenModifiersKey);
+}
+
+QMap<QString, int> SemanticTokens::defaultTokenTypesMap()
+{
+ QMap<QString, int> map;
+ map.insert("namespace", namespaceToken);
+ map.insert("type", typeToken);
+ map.insert("class", classToken);
+ map.insert("enum", enumToken);
+ map.insert("interface", interfaceToken);
+ map.insert("struct", structToken);
+ map.insert("typeParameter", typeParameterToken);
+ map.insert("parameter", parameterToken);
+ map.insert("variable", variableToken);
+ map.insert("property", propertyToken);
+ map.insert("enumMember", enumMemberToken);
+ map.insert("event", eventToken);
+ map.insert("function", functionToken);
+ map.insert("method", methodToken);
+ map.insert("macro", macroToken);
+ map.insert("keyword", keywordToken);
+ map.insert("modifier", modifierToken);
+ map.insert("comment", commentToken);
+ map.insert("string", stringToken);
+ map.insert("number", numberToken);
+ map.insert("regexp", regexpToken);
+ map.insert("operator", operatorToken);
+ return map;
+}
+
+QMap<QString, int> SemanticTokens::defaultTokenModifiersMap()
+{
+ QMap<QString, int> map;
+ map.insert("declaration", declarationModifier);
+ map.insert("definition", definitionModifier);
+ map.insert("readonly", readonlyModifier);
+ map.insert("static", staticModifier);
+ map.insert("deprecated", deprecatedModifier);
+ map.insert("abstract", abstractModifier);
+ map.insert("async", asyncModifier);
+ map.insert("modification", modificationModifier);
+ map.insert("documentation", documentationModifier);
+ map.insert("defaultLibrary", defaultLibraryModifier);
+ return map;
+}
+
+static int convertModifiers(int modifiersData, const QList<int> &tokenModifiers)
+{
+ int result = 0;
+ for (int i = 0; i < tokenModifiers.size() && modifiersData > 0; ++i) {
+ if (modifiersData & 0x1) {
+ const int modifier = tokenModifiers[i];
+ if (modifier > 0)
+ result |= modifier;
+ }
+ modifiersData = modifiersData >> 1;
+ }
+ return result;
+}
+
+QList<SemanticToken> SemanticTokens::toTokens(const QList<int> &tokenTypes,
+ const QList<int> &tokenModifiers) const
+{
+ const QList<int> &data = this->data();
+ if (data.size() % 5 != 0)
+ return {};
+ QList<SemanticToken> tokens;
+ tokens.reserve(int(data.size() / 5));
+ auto end = data.end();
+ for (auto it = data.begin(); it != end; it += 5) {
+ SemanticToken token;
+ token.deltaLine = *(it);
+ token.deltaStart = *(it + 1);
+ token.length = *(it + 2);
+ token.tokenIndex = *(it + 3);
+ token.tokenType = tokenTypes.value(token.tokenIndex, -1);
+ token.rawTokenModifiers = *(it + 4);
+ token.tokenModifiers = convertModifiers(token.rawTokenModifiers, tokenModifiers);
+ tokens << token;
+ }
+ return tokens;
+}
+
+bool SemanticTokensRangeParams::isValid() const
+{
+ return SemanticTokensParams::isValid() && contains(rangeKey);
+}
+
+SemanticTokensFullRequest::SemanticTokensFullRequest(const SemanticTokensParams &params)
+ : Request(methodName, params)
+{}
+
+SemanticTokensRangeRequest::SemanticTokensRangeRequest(const SemanticTokensRangeParams &params)
+ : Request(methodName, params)
+{}
+
+SemanticTokensResult::SemanticTokensResult(const QJsonValue &value)
+{
+ if (value.isObject())
+ emplace<SemanticTokens>(SemanticTokens(value.toObject()));
+ else
+ emplace<std::nullptr_t>(nullptr);
+}
+
+SemanticTokensFullDeltaRequest::SemanticTokensFullDeltaRequest(const SemanticTokensDeltaParams &params)
+ : Request(methodName, params)
+{}
+
+bool SemanticTokensDeltaParams::isValid() const
+{
+ return SemanticTokensParams::isValid() && contains(previousResultIdKey);
+}
+
+SemanticTokensDeltaResult::SemanticTokensDeltaResult(const QJsonValue &value)
+{
+ if (value.isObject()) {
+ QJsonObject object = value.toObject();
+ if (object.contains(editsKey))
+ emplace<SemanticTokensDelta>(object);
+ else
+ emplace<SemanticTokens>(object);
+ } else {
+ emplace<std::nullptr_t>(nullptr);
+ }
+}
+
+SemanticTokensRefreshRequest::SemanticTokensRefreshRequest()
+ : Request(methodName, nullptr)
+{}
+
+} // namespace lsp
diff --git a/src/shared/lsp/semantictokens.h b/src/shared/lsp/semantictokens.h
new file mode 100644
index 000000000..2dce74aa4
--- /dev/null
+++ b/src/shared/lsp/semantictokens.h
@@ -0,0 +1,230 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonkeys.h"
+#include "jsonobject.h"
+#include "jsonrpcmessages.h"
+#include "languageserverprotocol_global.h"
+#include "lsptypes.h"
+
+namespace lsp {
+
+struct LANGUAGESERVERPROTOCOL_EXPORT SemanticToken
+{
+ int deltaLine = 0;
+ int deltaStart = 0;
+ int length = 0;
+ int tokenIndex = 0;
+ int tokenType = 0;
+ int rawTokenModifiers = 0;
+ int tokenModifiers = 0;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensLegend : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // The token types a server uses.
+ QList<QString> tokenTypes() const { return array<QString>(tokenTypesKey); }
+ void setTokenTypes(const QList<QString> &tokenTypes) { insertArray(tokenTypesKey, tokenTypes); }
+
+ // The token modifiers a server uses.
+ QList<QString> tokenModifiers() const { return array<QString>(tokenModifiersKey); }
+ void setTokenModifiers(const QList<QString> &value) { insertArray(tokenModifiersKey, value); }
+
+ bool isValid() const override;
+};
+
+enum SemanticTokenTypes {
+ namespaceToken,
+ typeToken,
+ classToken,
+ enumToken,
+ interfaceToken,
+ structToken,
+ typeParameterToken,
+ parameterToken,
+ variableToken,
+ propertyToken,
+ enumMemberToken,
+ eventToken,
+ functionToken,
+ methodToken,
+ macroToken,
+ keywordToken,
+ modifierToken,
+ commentToken,
+ stringToken,
+ numberToken,
+ regexpToken,
+ operatorToken
+};
+
+enum SemanticTokenModifiers {
+ declarationModifier = 0x1,
+ definitionModifier = 0x2,
+ readonlyModifier = 0x4,
+ staticModifier = 0x8,
+ deprecatedModifier = 0x10,
+ abstractModifier = 0x20,
+ asyncModifier = 0x40,
+ modificationModifier = 0x80,
+ documentationModifier = 0x100,
+ defaultLibraryModifier = 0x200
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ bool isValid() const override { return contains(textDocumentKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensDeltaParams : public SemanticTokensParams
+{
+public:
+ using SemanticTokensParams::SemanticTokensParams;
+
+ QString previousResultId() const { return typedValue<QString>(previousResultIdKey); }
+ void setPreviousResultId(const QString &previousResultId)
+ {
+ insert(previousResultIdKey, previousResultId);
+ }
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensRangeParams : public SemanticTokensParams
+{
+public:
+ using SemanticTokensParams::SemanticTokensParams;
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokens : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ /**
+ * An optional result id. If provided and clients support delta updating
+ * the client will include the result id in the next semantic token request.
+ * A server can then instead of computing all semantic tokens again simply
+ * send a delta.
+ */
+ std::optional<QString> resultId() const { return optionalValue<QString>(resultIdKey); }
+ void setResultId(const QString &resultId) { insert(resultIdKey, resultId); }
+ void clearResultId() { remove(resultIdKey); }
+
+ /// The actual tokens.
+ QList<int> data() const { return array<int>(dataKey); }
+ void setData(const QList<int> &value) { insertArray(dataKey, value); }
+
+ bool isValid() const override { return contains(dataKey); }
+
+ QList<SemanticToken> toTokens(const QList<int> &tokenTypes,
+ const QList<int> &tokenModifiers) const;
+ static QMap<QString, int> defaultTokenTypesMap();
+ static QMap<QString, int> defaultTokenModifiersMap();
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensResult
+ : public std::variant<SemanticTokens, std::nullptr_t>
+{
+public:
+ using variant::variant;
+ explicit SemanticTokensResult(const QJsonValue &value);
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensFullRequest
+ : public Request<SemanticTokensResult, std::nullptr_t, SemanticTokensParams>
+{
+public:
+ explicit SemanticTokensFullRequest(const SemanticTokensParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/semanticTokens/full";
+};
+
+class SemanticTokensEdit : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ int start() const { return typedValue<int>(startKey); }
+ void setStart(int start) { insert(startKey, start); }
+
+ int deleteCount() const { return typedValue<int>(deleteCountKey); }
+ void setDeleteCount(int deleteCount) { insert(deleteCountKey, deleteCount); }
+
+ std::optional<QList<int>> data() const { return optionalArray<int>(dataKey); }
+ void setData(const QList<int> &value) { insertArray(dataKey, value); }
+ void clearData() { remove(dataKey); }
+
+ int dataSize() const { return contains(dataKey) ? value(dataKey).toArray().size() : 0; }
+
+ bool isValid() const override { return contains(dataKey) && contains(deleteCountKey); }
+};
+
+class SemanticTokensDelta : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString resultId() const { return typedValue<QString>(resultIdKey); }
+ void setResultId(const QString &resultId) { insert(resultIdKey, resultId); }
+
+ QList<SemanticTokensEdit> edits() const { return array<SemanticTokensEdit>(editsKey); }
+ void setEdits(const QList<SemanticTokensEdit> &edits) { insertArray(editsKey, edits); }
+
+ bool isValid() const override { return contains(resultIdKey) && contains(editsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensDeltaResult
+ : public std::variant<SemanticTokens, SemanticTokensDelta, std::nullptr_t>
+{
+public:
+ using variant::variant;
+ explicit SemanticTokensDeltaResult(const QJsonValue &value);
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensFullDeltaRequest
+ : public Request<SemanticTokensDeltaResult, std::nullptr_t, SemanticTokensDeltaParams>
+{
+public:
+ explicit SemanticTokensFullDeltaRequest(const SemanticTokensDeltaParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/semanticTokens/full/delta";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensRangeRequest
+ : public Request<SemanticTokensResult, std::nullptr_t, SemanticTokensRangeParams>
+{
+public:
+ explicit SemanticTokensRangeRequest(const SemanticTokensRangeParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/semanticTokens/range";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensRefreshRequest
+ : public Request<std::nullptr_t, std::nullptr_t, std::nullptr_t>
+{
+public:
+ explicit SemanticTokensRefreshRequest();
+ using Request::Request;
+ constexpr static const char methodName[] = "workspace/semanticTokens/refresh";
+};
+
+} // namespace lsp
diff --git a/src/shared/lsp/servercapabilities.cpp b/src/shared/lsp/servercapabilities.cpp
new file mode 100644
index 000000000..0923f76f3
--- /dev/null
+++ b/src/shared/lsp/servercapabilities.cpp
@@ -0,0 +1,386 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "servercapabilities.h"
+
+namespace lsp {
+
+std::optional<ServerCapabilities::TextDocumentSync> ServerCapabilities::textDocumentSync() const
+{
+ const QJsonValue &sync = value(textDocumentSyncKey);
+ if (sync.isUndefined())
+ return std::nullopt;
+ return std::make_optional(sync.isDouble()
+ ? TextDocumentSync(sync.toInt())
+ : TextDocumentSync(TextDocumentSyncOptions(sync.toObject())));
+}
+
+void ServerCapabilities::setTextDocumentSync(const ServerCapabilities::TextDocumentSync &textDocumentSync)
+{
+ insertVariant<TextDocumentSyncOptions, int>(textDocumentSyncKey, textDocumentSync);
+}
+
+TextDocumentSyncKind ServerCapabilities::textDocumentSyncKindHelper()
+{
+ if (std::optional<TextDocumentSync> sync = textDocumentSync()) {
+ if (auto kind = std::get_if<int>(&*sync))
+ return static_cast<TextDocumentSyncKind>(*kind);
+ if (auto options = std::get_if<TextDocumentSyncOptions>(&*sync)) {
+ if (const std::optional<int> &change = options->change())
+ return static_cast<TextDocumentSyncKind>(*change);
+ }
+ }
+ return TextDocumentSyncKind::None;
+}
+
+std::optional<std::variant<bool, WorkDoneProgressOptions>> ServerCapabilities::hoverProvider()
+ const
+{
+ using RetType = std::variant<bool, WorkDoneProgressOptions>;
+ const QJsonValue &provider = value(hoverProviderKey);
+ if (provider.isBool())
+ return std::make_optional(RetType(provider.toBool()));
+ if (provider.isObject())
+ return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject())));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setHoverProvider(
+ const std::variant<bool, WorkDoneProgressOptions> &hoverProvider)
+{
+ insertVariant<bool, WorkDoneProgressOptions>(hoverProviderKey, hoverProvider);
+}
+
+std::optional<std::variant<bool, ServerCapabilities::RegistrationOptions>>
+ServerCapabilities::definitionProvider() const
+{
+ using RetType = std::variant<bool, ServerCapabilities::RegistrationOptions>;
+ const QJsonValue &provider = value(definitionProviderKey);
+ if (provider.isUndefined() || !(provider.isBool() || provider.isObject()))
+ return std::nullopt;
+ return std::make_optional(provider.isBool()
+ ? RetType(provider.toBool())
+ : RetType(RegistrationOptions(provider.toObject())));
+}
+
+void ServerCapabilities::setDefinitionProvider(
+ const std::variant<bool, RegistrationOptions> &definitionProvider)
+{
+ insertVariant<bool, RegistrationOptions>(definitionProviderKey, definitionProvider);
+}
+
+std::optional<std::variant<bool, ServerCapabilities::RegistrationOptions>>
+ServerCapabilities::typeDefinitionProvider() const
+{
+ using RetType = std::variant<bool, ServerCapabilities::RegistrationOptions>;
+ const QJsonValue &provider = value(typeDefinitionProviderKey);
+ if (provider.isUndefined() || !(provider.isBool() || provider.isObject()))
+ return std::nullopt;
+ return std::make_optional(provider.isBool()
+ ? RetType(provider.toBool())
+ : RetType(RegistrationOptions(provider.toObject())));
+}
+
+void ServerCapabilities::setTypeDefinitionProvider(
+ const std::variant<bool, ServerCapabilities::RegistrationOptions> &typeDefinitionProvider)
+{
+ insertVariant<bool, ServerCapabilities::RegistrationOptions>(typeDefinitionProviderKey,
+ typeDefinitionProvider);
+}
+
+std::optional<std::variant<bool, ServerCapabilities::RegistrationOptions>>
+ServerCapabilities::implementationProvider() const
+{
+ using RetType = std::variant<bool, ServerCapabilities::RegistrationOptions>;
+ const QJsonValue &provider = value(implementationProviderKey);
+ if (provider.isUndefined() || !(provider.isBool() || provider.isObject()))
+ return std::nullopt;
+ return std::make_optional(provider.isBool()
+ ? RetType(provider.toBool())
+ : RetType(RegistrationOptions(provider.toObject())));
+}
+
+void ServerCapabilities::setImplementationProvider(
+ const std::variant<bool, ServerCapabilities::RegistrationOptions> &implementationProvider)
+{
+ insertVariant<bool, RegistrationOptions>(implementationProviderKey, implementationProvider);
+}
+
+std::optional<std::variant<bool, WorkDoneProgressOptions>>
+ServerCapabilities::referencesProvider() const
+{
+ using RetType = std::variant<bool, WorkDoneProgressOptions>;
+ const QJsonValue &provider = value(referencesProviderKey);
+ if (provider.isBool())
+ return std::make_optional(RetType(provider.toBool()));
+ if (provider.isObject())
+ return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject())));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setReferencesProvider(
+ const std::variant<bool, WorkDoneProgressOptions> &referencesProvider)
+{
+ insertVariant<bool, WorkDoneProgressOptions>(referencesProviderKey,
+ referencesProvider);
+}
+
+std::optional<std::variant<bool, WorkDoneProgressOptions>>
+ServerCapabilities::documentHighlightProvider() const
+{
+ using RetType = std::variant<bool, WorkDoneProgressOptions>;
+ const QJsonValue &provider = value(documentHighlightProviderKey);
+ if (provider.isBool())
+ return std::make_optional(RetType(provider.toBool()));
+ if (provider.isObject())
+ return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject())));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setDocumentHighlightProvider(
+ const std::variant<bool, WorkDoneProgressOptions> &documentHighlightProvider)
+{
+ insertVariant<bool, WorkDoneProgressOptions>(documentHighlightProviderKey,
+ documentHighlightProvider);
+}
+
+std::optional<std::variant<bool, WorkDoneProgressOptions>>
+ServerCapabilities::documentSymbolProvider() const
+{
+ using RetType = std::variant<bool, WorkDoneProgressOptions>;
+ const QJsonValue &provider = value(documentSymbolProviderKey);
+ if (provider.isBool())
+ return std::make_optional(RetType(provider.toBool()));
+ if (provider.isObject())
+ return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject())));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setDocumentSymbolProvider(
+ std::variant<bool, WorkDoneProgressOptions> documentSymbolProvider)
+{
+ insertVariant<bool, WorkDoneProgressOptions>(documentSymbolProviderKey,
+ documentSymbolProvider);
+}
+
+std::optional<SemanticTokensOptions> ServerCapabilities::semanticTokensProvider() const
+{
+ return optionalValue<SemanticTokensOptions>(semanticTokensProviderKey);
+}
+
+void ServerCapabilities::setSemanticTokensProvider(
+ const SemanticTokensOptions &semanticTokensProvider)
+{
+ insert(semanticTokensProviderKey, semanticTokensProvider);
+}
+
+std::optional<std::variant<bool, WorkDoneProgressOptions> >
+ServerCapabilities::callHierarchyProvider() const
+{
+ const QJsonValue &provider = value(callHierarchyProviderKey);
+ if (provider.isBool())
+ return provider.toBool();
+ else if (provider.isObject())
+ return WorkDoneProgressOptions(provider.toObject());
+ return std::nullopt;
+}
+
+void ServerCapabilities::setCallHierarchyProvider(
+ const std::variant<bool, WorkDoneProgressOptions> &callHierarchyProvider)
+{
+ QJsonValue val;
+ if (std::holds_alternative<bool>(callHierarchyProvider))
+ val = std::get<bool>(callHierarchyProvider);
+ else if (std::holds_alternative<WorkDoneProgressOptions>(callHierarchyProvider))
+ val = QJsonObject(std::get<WorkDoneProgressOptions>(callHierarchyProvider));
+ insert(callHierarchyProviderKey, val);
+}
+
+std::optional<std::variant<bool, WorkDoneProgressOptions>>
+ServerCapabilities::workspaceSymbolProvider() const
+{
+ using RetType = std::variant<bool, WorkDoneProgressOptions>;
+ const QJsonValue &provider = value(workspaceSymbolProviderKey);
+ if (provider.isBool())
+ return std::make_optional(RetType(provider.toBool()));
+ if (provider.isObject())
+ return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject())));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setWorkspaceSymbolProvider(
+ std::variant<bool, WorkDoneProgressOptions> workspaceSymbolProvider)
+{
+ insertVariant<bool, WorkDoneProgressOptions>(workspaceSymbolProviderKey,
+ workspaceSymbolProvider);
+}
+
+std::optional<std::variant<bool, CodeActionOptions>> ServerCapabilities::codeActionProvider() const
+{
+ const QJsonValue &provider = value(codeActionProviderKey);
+ if (provider.isBool())
+ return std::make_optional(std::variant<bool, CodeActionOptions>(provider.toBool()));
+ if (provider.isObject()) {
+ CodeActionOptions options(provider);
+ if (options.isValid())
+ return std::make_optional(std::variant<bool, CodeActionOptions>(options));
+ }
+ return std::nullopt;
+}
+
+std::optional<std::variant<bool, WorkDoneProgressOptions>>
+ServerCapabilities::documentFormattingProvider() const
+{
+ using RetType = std::variant<bool, WorkDoneProgressOptions>;
+ const QJsonValue &provider = value(documentFormattingProviderKey);
+ if (provider.isBool())
+ return std::make_optional(RetType(provider.toBool()));
+ if (provider.isObject())
+ return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject())));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setDocumentFormattingProvider(
+ const std::variant<bool, WorkDoneProgressOptions> &documentFormattingProvider)
+{
+ insertVariant<bool, WorkDoneProgressOptions>(documentFormattingProviderKey,
+ documentFormattingProvider);
+}
+
+std::optional<std::variant<bool, WorkDoneProgressOptions>>
+ServerCapabilities::documentRangeFormattingProvider() const
+{
+ using RetType = std::variant<bool, WorkDoneProgressOptions>;
+ const QJsonValue &provider = value(documentRangeFormattingProviderKey);
+ if (provider.isBool())
+ return std::make_optional(RetType(provider.toBool()));
+ if (provider.isObject())
+ return std::make_optional(RetType(WorkDoneProgressOptions(provider.toObject())));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setDocumentRangeFormattingProvider(
+ std::variant<bool, WorkDoneProgressOptions> documentRangeFormattingProvider)
+{
+ insertVariant<bool, WorkDoneProgressOptions>(documentRangeFormattingProviderKey,
+ documentRangeFormattingProvider);
+}
+
+std::optional<std::variant<ServerCapabilities::RenameOptions, bool>> ServerCapabilities::renameProvider() const
+{
+ using RetType = std::variant<ServerCapabilities::RenameOptions, bool>;
+ const QJsonValue &localValue = value(renameProviderKey);
+ if (localValue.isBool())
+ return RetType(localValue.toBool());
+ if (localValue.isObject())
+ return RetType(RenameOptions(localValue.toObject()));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setRenameProvider(std::variant<ServerCapabilities::RenameOptions, bool> renameProvider)
+{
+ insertVariant<RenameOptions, bool>(renameProviderKey, renameProvider);
+}
+
+std::optional<std::variant<bool, JsonObject>> ServerCapabilities::colorProvider() const
+{
+ using RetType = std::variant<bool, JsonObject>;
+ const QJsonValue &localValue = value(colorProviderKey);
+ if (localValue.isBool())
+ return RetType(localValue.toBool());
+ if (localValue.isObject())
+ return RetType(JsonObject(localValue.toObject()));
+ return std::nullopt;
+}
+
+void ServerCapabilities::setColorProvider(std::variant<bool, JsonObject> colorProvider)
+{
+ insertVariant<bool, JsonObject>(renameProviderKey, colorProvider);
+}
+
+std::optional<std::variant<QString, bool> >
+ServerCapabilities::WorkspaceServerCapabilities::WorkspaceFoldersCapabilities::changeNotifications() const
+{
+ using RetType = std::variant<QString, bool>;
+ const QJsonValue &change = value(changeNotificationsKey);
+ if (change.isUndefined())
+ return std::nullopt;
+ return std::make_optional(change.isBool() ? RetType(change.toBool())
+ : RetType(change.toString()));
+}
+
+void ServerCapabilities::WorkspaceServerCapabilities::WorkspaceFoldersCapabilities::setChangeNotifications(
+ std::variant<QString, bool> changeNotifications)
+{
+ insertVariant<QString, bool>(changeNotificationsKey, changeNotifications);
+}
+
+bool TextDocumentRegistrationOptions::filterApplies(const Utils::FilePath &fileName) const
+{
+ Q_UNUSED(fileName)
+ return true;
+}
+
+bool ServerCapabilities::ExecuteCommandOptions::isValid() const
+{
+ return WorkDoneProgressOptions::isValid() && contains(commandsKey);
+}
+
+bool CodeActionOptions::isValid() const
+{
+ return WorkDoneProgressOptions::isValid() && contains(codeActionKindsKey);
+}
+
+std::optional<std::variant<bool, QJsonObject>> SemanticTokensOptions::range() const
+{
+ using RetType = std::variant<bool, QJsonObject>;
+ const QJsonValue &rangeOptions = value(rangeKey);
+ if (rangeOptions.isBool())
+ return RetType(rangeOptions.toBool());
+ if (rangeOptions.isObject())
+ return RetType(rangeOptions.toObject());
+ return std::nullopt;
+}
+
+void SemanticTokensOptions::setRange(const std::variant<bool, QJsonObject> &range)
+{
+ insertVariant<bool, QJsonObject>(rangeKey, range);
+}
+
+std::optional<std::variant<bool, SemanticTokensOptions::FullSemanticTokenOptions>>
+SemanticTokensOptions::full() const
+{
+ using RetType = std::variant<bool, SemanticTokensOptions::FullSemanticTokenOptions>;
+ const QJsonValue &fullOptions = value(fullKey);
+ if (fullOptions.isBool())
+ return RetType(fullOptions.toBool());
+ if (fullOptions.isObject())
+ return RetType(FullSemanticTokenOptions(fullOptions.toObject()));
+ return std::nullopt;
+}
+
+void SemanticTokensOptions::setFull(
+ const std::variant<bool, SemanticTokensOptions::FullSemanticTokenOptions> &full)
+{
+ insertVariant<bool, FullSemanticTokenOptions>(fullKey, full);
+}
+
+SemanticRequestTypes SemanticTokensOptions::supportedRequests() const
+{
+ SemanticRequestTypes result;
+ QJsonValue rangeValue = value(rangeKey);
+ if (rangeValue.isObject() || rangeValue.toBool())
+ result |= SemanticRequestType::Range;
+ QJsonValue fullValue = value(fullKey);
+ if (fullValue.isObject()) {
+ SemanticTokensOptions::FullSemanticTokenOptions options(fullValue.toObject());
+ if (options.delta().value_or(false))
+ result |= SemanticRequestType::FullDelta;
+ result |= SemanticRequestType::Full;
+ } else if (fullValue.toBool()) {
+ result |= SemanticRequestType::Full;
+ }
+ return result;
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/servercapabilities.h b/src/shared/lsp/servercapabilities.h
new file mode 100644
index 000000000..03c0371e8
--- /dev/null
+++ b/src/shared/lsp/servercapabilities.h
@@ -0,0 +1,435 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "lsptypes.h"
+#include "semantictokens.h"
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkDoneProgressOptions : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ std::optional<bool> workDoneProgress() const { return optionalValue<bool>(workDoneProgressKey); }
+ void setWorkDoneProgress(bool workDoneProgress) { insert(workDoneProgressKey, workDoneProgress); }
+ void clearWorkDoneProgress() { remove(workDoneProgressKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ResolveProviderOption : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ std::optional<bool> resolveProvider() const { return optionalValue<bool>(resolveProviderKey); }
+ void setResolveProvider(bool resolveProvider) { insert(resolveProviderKey, resolveProvider); }
+ void clearResolveProvider() { remove(resolveProviderKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentRegistrationOptions : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ LanguageClientArray<DocumentFilter> documentSelector() const
+ { return clientArray<DocumentFilter>(documentSelectorKey); }
+ void setDocumentSelector(const LanguageClientArray<DocumentFilter> &documentSelector)
+ { insert(documentSelectorKey, documentSelector.toJson()); }
+
+ bool filterApplies(const Utils::FilePath &fileName) const;
+
+ bool isValid() const override { return contains(documentSelectorKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT SaveOptions : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // The client is supposed to include the content on save.
+ std::optional<bool> includeText() const { return optionalValue<bool>(includeTextKey); }
+ void setIncludeText(bool includeText) { insert(includeTextKey, includeText); }
+ void clearIncludeText() { remove(includeTextKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentSyncOptions : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // Open and close notifications are sent to the server.
+ std::optional<bool> openClose() const { return optionalValue<bool>(openCloseKey); }
+ void setOpenClose(bool openClose) { insert(openCloseKey, openClose); }
+ void clearOpenClose() { remove(openCloseKey); }
+
+ // Change notifications are sent to the server. See TextDocumentSyncKind.None,
+ // TextDocumentSyncKind.Full and TextDocumentSyncKind.Incremental.
+ std::optional<int> change() const { return optionalValue<int>(changeKey); }
+ void setChange(int change) { insert(changeKey, change); }
+ void clearChange() { remove(changeKey); }
+
+ // Will save notifications are sent to the server.
+ std::optional<bool> willSave() const { return optionalValue<bool>(willSaveKey); }
+ void setWillSave(bool willSave) { insert(willSaveKey, willSave); }
+ void clearWillSave() { remove(willSaveKey); }
+
+ // Will save wait until requests are sent to the server.
+ std::optional<bool> willSaveWaitUntil() const
+ { return optionalValue<bool>(willSaveWaitUntilKey); }
+ void setWillSaveWaitUntil(bool willSaveWaitUntil)
+ { insert(willSaveWaitUntilKey, willSaveWaitUntil); }
+ void clearWillSaveWaitUntil() { remove(willSaveWaitUntilKey); }
+
+ // Save notifications are sent to the server.
+ std::optional<SaveOptions> save() const { return optionalValue<SaveOptions>(saveKey); }
+ void setSave(const SaveOptions &save) { insert(saveKey, save); }
+ void clearSave() { remove(saveKey); }
+};
+
+enum class TextDocumentSyncKind
+{
+ // Documents should not be synced at all.
+ None = 0,
+ // Documents are synced by always sending the full content of the document.
+ Full = 1,
+ // Documents are synced by sending the full content on open.
+ // After that only incremental updates to the document are send.
+ Incremental = 2
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT CodeActionOptions : public WorkDoneProgressOptions
+{
+public:
+ using WorkDoneProgressOptions::WorkDoneProgressOptions;
+
+ QList<QString> codeActionKinds() const { return array<QString>(codeActionKindsKey); }
+ void setCodeActionKinds(const QList<QString> &codeActionKinds)
+ { insertArray(codeActionKindsKey, codeActionKinds); }
+
+ bool isValid() const override;
+};
+
+enum class SemanticRequestType {
+ None = 0x0,
+ Full = 0x1,
+ FullDelta = 0x2,
+ Range = 0x4
+};
+Q_DECLARE_FLAGS(SemanticRequestTypes, SemanticRequestType)
+
+class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensOptions : public WorkDoneProgressOptions
+{
+public:
+ using WorkDoneProgressOptions::WorkDoneProgressOptions;
+
+ /// The legend used by the server
+ SemanticTokensLegend legend() const { return typedValue<SemanticTokensLegend>(legendKey); }
+ void setLegend(const SemanticTokensLegend &legend) { insert(legendKey, legend); }
+
+ /// Server supports providing semantic tokens for a specific range of a document.
+ std::optional<std::variant<bool, QJsonObject>> range() const;
+ void setRange(const std::variant<bool, QJsonObject> &range);
+ void clearRange() { remove(rangeKey); }
+
+ class FullSemanticTokenOptions : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ /// The server supports deltas for full documents.
+ std::optional<bool> delta() const { return optionalValue<bool>(deltaKey); }
+ void setDelta(bool delta) { insert(deltaKey, delta); }
+ void clearDelta() { remove(deltaKey); }
+ };
+
+ /// Server supports providing semantic tokens for a full document.
+ std::optional<std::variant<bool, FullSemanticTokenOptions>> full() const;
+ void setFull(const std::variant<bool, FullSemanticTokenOptions> &full);
+ void clearFull() { remove(fullKey); }
+
+ bool isValid() const override { return contains(legendKey); }
+
+ SemanticRequestTypes supportedRequests() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ServerCapabilities : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ // Defines how the host (editor) should sync document changes to the language server.
+
+ class LANGUAGESERVERPROTOCOL_EXPORT CompletionOptions : public WorkDoneProgressOptions
+ {
+ public:
+ using WorkDoneProgressOptions::WorkDoneProgressOptions;
+
+ // The characters that trigger completion automatically.
+ std::optional<QList<QString>> triggerCharacters() const
+ { return optionalArray<QString>(triggerCharactersKey); }
+ void setTriggerCharacters(const QList<QString> &triggerCharacters)
+ { insertArray(triggerCharactersKey, triggerCharacters); }
+ void clearTriggerCharacters() { remove(triggerCharactersKey); }
+
+ std::optional<bool> resolveProvider() const { return optionalValue<bool>(resolveProviderKey); }
+ void setResolveProvider(bool resolveProvider) { insert(resolveProviderKey, resolveProvider); }
+ void clearResolveProvider() { remove(resolveProviderKey); }
+ };
+
+ class LANGUAGESERVERPROTOCOL_EXPORT SignatureHelpOptions : public WorkDoneProgressOptions
+ {
+ public:
+ using WorkDoneProgressOptions::WorkDoneProgressOptions;
+
+ // The characters that trigger signature help automatically.
+ std::optional<QList<QString>> triggerCharacters() const
+ { return optionalArray<QString>(triggerCharactersKey); }
+ void setTriggerCharacters(const QList<QString> &triggerCharacters)
+ { insertArray(triggerCharactersKey, triggerCharacters); }
+ void clearTriggerCharacters() { remove(triggerCharactersKey); }
+ };
+
+ using CodeLensOptions = ResolveProviderOption;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT DocumentOnTypeFormattingOptions : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ // A character on which formatting should be triggered, like `}`.
+ QString firstTriggerCharacter() const { return typedValue<QString>(firstTriggerCharacterKey); }
+ void setFirstTriggerCharacter(QString firstTriggerCharacter)
+ { insert(firstTriggerCharacterKey, firstTriggerCharacter); }
+
+ // More trigger characters.
+ std::optional<QList<QString>> moreTriggerCharacter() const
+ { return optionalArray<QString>(moreTriggerCharacterKey); }
+ void setMoreTriggerCharacter(const QList<QString> &moreTriggerCharacter)
+ { insertArray(moreTriggerCharacterKey, moreTriggerCharacter); }
+ void clearMoreTriggerCharacter() { remove(moreTriggerCharacterKey); }
+
+ bool isValid() const override { return contains(firstTriggerCharacterKey); }
+ };
+
+ using DocumentLinkOptions = ResolveProviderOption;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandOptions : public WorkDoneProgressOptions
+ {
+ public:
+ using WorkDoneProgressOptions::WorkDoneProgressOptions;
+
+ QList<QString> commands() const { return array<QString>(commandsKey); }
+ void setCommands(const QList<QString> &commands) { insertArray(commandsKey, commands); }
+
+ bool isValid() const override;
+ };
+
+ using ColorProviderOptions = JsonObject;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT StaticRegistrationOptions : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ // The id used to register the request. The id can be used to deregister
+ // the request again. See also Registration#id.
+ std::optional<QString> id() const { return optionalValue<QString>(idKey); }
+ void setId(const QString &id) { insert(idKey, id); }
+ void clearId() { remove(idKey); }
+ };
+
+ // Defines how text documents are synced. Is either a detailed structure defining each
+ // notification or for backwards compatibility the TextDocumentSyncKind number.
+ using TextDocumentSync = std::variant<TextDocumentSyncOptions, int>;
+ std::optional<TextDocumentSync> textDocumentSync() const;
+ void setTextDocumentSync(const TextDocumentSync &textDocumentSync);
+ void clearTextDocumentSync() { remove(textDocumentSyncKey); }
+
+ TextDocumentSyncKind textDocumentSyncKindHelper();
+
+ // The server provides hover support.
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> hoverProvider() const;
+ void setHoverProvider(const std::variant<bool, WorkDoneProgressOptions> &hoverProvider);
+ void clearHoverProvider() { remove(hoverProviderKey); }
+
+ // The server provides completion support.
+ std::optional<CompletionOptions> completionProvider() const
+ { return optionalValue<CompletionOptions>(completionProviderKey); }
+ void setCompletionProvider(const CompletionOptions &completionProvider)
+ { insert(completionProviderKey, completionProvider); }
+ void clearCompletionProvider() { remove(completionProviderKey); }
+
+ // The server provides signature help support.
+ std::optional<SignatureHelpOptions> signatureHelpProvider() const
+ { return optionalValue<SignatureHelpOptions>(signatureHelpProviderKey); }
+ void setSignatureHelpProvider(const SignatureHelpOptions &signatureHelpProvider)
+ { insert(signatureHelpProviderKey, signatureHelpProvider); }
+ void clearSignatureHelpProvider() { remove(signatureHelpProviderKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT RegistrationOptions : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ LanguageClientArray<DocumentFilter> documentSelector() const
+ { return clientArray<DocumentFilter>(documentSelectorKey); }
+ void setDocumentSelector(const LanguageClientArray<DocumentFilter> &documentSelector)
+ { insert(documentSelectorKey, documentSelector.toJson()); }
+
+ bool filterApplies(const Utils::FilePath &fileName) const;
+
+ // The id used to register the request. The id can be used to deregister
+ // the request again. See also Registration#id.
+ std::optional<QString> id() const { return optionalValue<QString>(idKey); }
+ void setId(const QString &id) { insert(idKey, id); }
+ void clearId() { remove(idKey); }
+
+ bool isValid() const override { return contains(documentSelectorKey); }
+ };
+
+ // The server provides goto definition support.
+ std::optional<std::variant<bool, RegistrationOptions>> definitionProvider() const;
+ void setDefinitionProvider(const std::variant<bool, RegistrationOptions> &typeDefinitionProvider);
+ void clearDefinitionProvider() { remove(typeDefinitionProviderKey); }
+
+ // The server provides Goto Type Definition support.
+ std::optional<std::variant<bool, RegistrationOptions>> typeDefinitionProvider() const;
+ void setTypeDefinitionProvider(const std::variant<bool, RegistrationOptions> &typeDefinitionProvider);
+ void clearTypeDefinitionProvider() { remove(typeDefinitionProviderKey); }
+
+ // The server provides Goto Implementation support.
+ std::optional<std::variant<bool, RegistrationOptions>> implementationProvider() const;
+ void setImplementationProvider(const std::variant<bool, RegistrationOptions> &implementationProvider);
+ void clearImplementationProvider() { remove(implementationProviderKey); }
+
+ // The server provides find references support.
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> referencesProvider() const;
+ void setReferencesProvider(const std::variant<bool, WorkDoneProgressOptions> &referencesProvider);
+ void clearReferencesProvider() { remove(referencesProviderKey); }
+
+ // The server provides document highlight support.
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> documentHighlightProvider() const;
+ void setDocumentHighlightProvider(
+ const std::variant<bool, WorkDoneProgressOptions> &documentHighlightProvider);
+ void clearDocumentHighlightProvider() { remove(documentHighlightProviderKey); }
+
+ // The server provides document symbol support.
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> documentSymbolProvider() const;
+ void setDocumentSymbolProvider(std::variant<bool, WorkDoneProgressOptions> documentSymbolProvider);
+ void clearDocumentSymbolProvider() { remove(documentSymbolProviderKey); }
+
+ std::optional<SemanticTokensOptions> semanticTokensProvider() const;
+ void setSemanticTokensProvider(const SemanticTokensOptions &semanticTokensProvider);
+ void clearSemanticTokensProvider() { remove(semanticTokensProviderKey); }
+
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> callHierarchyProvider() const;
+ void setCallHierarchyProvider(const std::variant<bool, WorkDoneProgressOptions> &callHierarchyProvider);
+ void clearCallHierarchyProvider() { remove(callHierarchyProviderKey); }
+
+ // The server provides workspace symbol support.
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> workspaceSymbolProvider() const;
+ void setWorkspaceSymbolProvider(std::variant<bool, WorkDoneProgressOptions> workspaceSymbolProvider);
+ void clearWorkspaceSymbolProvider() { remove(workspaceSymbolProviderKey); }
+
+ // The server provides code actions.
+ std::optional<std::variant<bool, CodeActionOptions>> codeActionProvider() const;
+ void setCodeActionProvider(bool codeActionProvider)
+ { insert(codeActionProviderKey, codeActionProvider); }
+ void setCodeActionProvider(CodeActionOptions options)
+ { insert(codeActionProviderKey, options); }
+ void clearCodeActionProvider() { remove(codeActionProviderKey); }
+
+ // The server provides code lens.
+ std::optional<CodeLensOptions> codeLensProvider() const
+ { return optionalValue<CodeLensOptions>(codeLensProviderKey); }
+ void setCodeLensProvider(CodeLensOptions codeLensProvider)
+ { insert(codeLensProviderKey, codeLensProvider); }
+ void clearCodeLensProvider() { remove(codeLensProviderKey); }
+
+ // The server provides document formatting.
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> documentFormattingProvider() const;
+ void setDocumentFormattingProvider(
+ const std::variant<bool, WorkDoneProgressOptions> &documentFormattingProvider);
+ void clearDocumentFormattingProvider() { remove(documentFormattingProviderKey); }
+
+ // The server provides document formatting on typing.
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> documentRangeFormattingProvider() const;
+ void setDocumentRangeFormattingProvider(std::variant<bool, WorkDoneProgressOptions> documentRangeFormattingProvider);
+ void clearDocumentRangeFormattingProvider() { remove(documentRangeFormattingProviderKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT RenameOptions : public WorkDoneProgressOptions
+ {
+ public:
+ using WorkDoneProgressOptions::WorkDoneProgressOptions;
+
+ // Renames should be checked and tested before being executed.
+ std::optional<bool> prepareProvider() const { return optionalValue<bool>(prepareProviderKey); }
+ void setPrepareProvider(bool prepareProvider) { insert(prepareProviderKey, prepareProvider); }
+ void clearPrepareProvider() { remove(prepareProviderKey); }
+ };
+
+ // The server provides rename support.
+ std::optional<std::variant<RenameOptions, bool>> renameProvider() const;
+ void setRenameProvider(std::variant<RenameOptions,bool> renameProvider);
+ void clearRenameProvider() { remove(renameProviderKey); }
+
+ // The server provides document link support.
+ std::optional<DocumentLinkOptions> documentLinkProvider() const
+ { return optionalValue<DocumentLinkOptions>(documentLinkProviderKey); }
+ void setDocumentLinkProvider(const DocumentLinkOptions &documentLinkProvider)
+ { insert(documentLinkProviderKey, documentLinkProvider); }
+ void clearDocumentLinkProvider() { remove(documentLinkProviderKey); }
+
+ // The server provides color provider support.
+ std::optional<std::variant<bool, JsonObject>> colorProvider() const;
+ void setColorProvider(std::variant<bool, JsonObject> colorProvider);
+ void clearColorProvider() { remove(colorProviderKey); }
+
+ // The server provides execute command support.
+ std::optional<ExecuteCommandOptions> executeCommandProvider() const
+ { return optionalValue<ExecuteCommandOptions>(executeCommandProviderKey); }
+ void setExecuteCommandProvider(ExecuteCommandOptions executeCommandProvider)
+ { insert(executeCommandProviderKey, executeCommandProvider); }
+ void clearExecuteCommandProvider() { remove(executeCommandProviderKey); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceServerCapabilities : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceFoldersCapabilities : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ // The server has support for workspace folders
+ std::optional<bool> supported() const { return optionalValue<bool>(supportedKey); }
+ void setSupported(bool supported) { insert(supportedKey, supported); }
+ void clearSupported() { remove(supportedKey); }
+
+ std::optional<std::variant<QString, bool>> changeNotifications() const;
+ void setChangeNotifications(std::variant<QString, bool> changeNotifications);
+ void clearChangeNotifications() { remove(changeNotificationsKey); }
+ };
+
+ std::optional<WorkspaceFoldersCapabilities> workspaceFolders() const
+ { return optionalValue<WorkspaceFoldersCapabilities>(workspaceFoldersKey); }
+ void setWorkspaceFolders(const WorkspaceFoldersCapabilities &workspaceFolders)
+ { insert(workspaceFoldersKey, workspaceFolders); }
+ void clearWorkspaceFolders() { remove(workspaceFoldersKey); }
+ };
+
+ std::optional<WorkspaceServerCapabilities> workspace() const
+ { return optionalValue<WorkspaceServerCapabilities>(workspaceKey); }
+ void setWorkspace(const WorkspaceServerCapabilities &workspace)
+ { insert(workspaceKey, workspace); }
+ void clearWorkspace() { remove(workspaceKey); }
+
+ std::optional<JsonObject> experimental() const { return optionalValue<JsonObject>(experimentalKey); }
+ void setExperimental(const JsonObject &experimental) { insert(experimentalKey, experimental); }
+ void clearExperimental() { remove(experimentalKey); }
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/shutdownmessages.cpp b/src/shared/lsp/shutdownmessages.cpp
new file mode 100644
index 000000000..21c41c1d3
--- /dev/null
+++ b/src/shared/lsp/shutdownmessages.cpp
@@ -0,0 +1,13 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "shutdownmessages.h"
+
+namespace lsp {
+
+constexpr const char ShutdownRequest::methodName[];
+constexpr const char ExitNotification::methodName[];
+ShutdownRequest::ShutdownRequest() : Request(methodName, nullptr) { }
+ExitNotification::ExitNotification() : Notification(methodName) { }
+
+} // namespace lsp
diff --git a/src/shared/lsp/shutdownmessages.h b/src/shared/lsp/shutdownmessages.h
new file mode 100644
index 000000000..2e8da4c66
--- /dev/null
+++ b/src/shared/lsp/shutdownmessages.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT ShutdownRequest : public Request<
+ std::nullptr_t, std::nullptr_t, std::nullptr_t>
+{
+public:
+ ShutdownRequest();
+ using Request::Request;
+ constexpr static const char methodName[] = "shutdown";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ExitNotification : public Notification<std::nullptr_t>
+{
+public:
+ ExitNotification();
+ using Notification::Notification;
+ constexpr static const char methodName[] = "exit";
+
+ bool parametersAreValid(QString * /*errorMessage*/) const final { return true; }
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/textsynchronization.cpp b/src/shared/lsp/textsynchronization.cpp
new file mode 100644
index 000000000..ba9ee2f32
--- /dev/null
+++ b/src/shared/lsp/textsynchronization.cpp
@@ -0,0 +1,84 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "textsynchronization.h"
+
+namespace lsp {
+
+constexpr const char DidOpenTextDocumentNotification::methodName[];
+constexpr const char DidChangeTextDocumentNotification::methodName[];
+constexpr const char WillSaveTextDocumentNotification::methodName[];
+constexpr const char WillSaveWaitUntilTextDocumentRequest::methodName[];
+constexpr const char DidSaveTextDocumentNotification::methodName[];
+constexpr const char DidCloseTextDocumentNotification::methodName[];
+
+DidOpenTextDocumentNotification::DidOpenTextDocumentNotification(
+ const DidOpenTextDocumentParams &params)
+ : Notification(methodName, params)
+{ }
+
+DidChangeTextDocumentNotification::DidChangeTextDocumentNotification(
+ const DidChangeTextDocumentParams &params)
+ : DidChangeTextDocumentNotification(methodName, params)
+{ }
+
+WillSaveTextDocumentNotification::WillSaveTextDocumentNotification(
+ const WillSaveTextDocumentParams &params)
+ : Notification(methodName, params)
+{ }
+
+WillSaveWaitUntilTextDocumentRequest::WillSaveWaitUntilTextDocumentRequest(const WillSaveTextDocumentParams &params)
+ : Request(methodName, params)
+{ }
+
+DidSaveTextDocumentNotification::DidSaveTextDocumentNotification(
+ const DidSaveTextDocumentParams &params)
+ : Notification(methodName, params)
+{ }
+
+DidCloseTextDocumentNotification::DidCloseTextDocumentNotification(
+ const DidCloseTextDocumentParams &params)
+ : Notification(methodName, params)
+{ }
+
+DidChangeTextDocumentParams::DidChangeTextDocumentParams()
+ : DidChangeTextDocumentParams(VersionedTextDocumentIdentifier())
+{ }
+
+DidChangeTextDocumentParams::DidChangeTextDocumentParams(
+ const VersionedTextDocumentIdentifier &docId, const QString &text)
+{
+ setTextDocument(docId);
+ setContentChanges({TextDocumentContentChangeEvent(text)});
+}
+
+DidOpenTextDocumentParams::DidOpenTextDocumentParams(const TextDocumentItem &document)
+{
+ setTextDocument(document);
+}
+
+DidCloseTextDocumentParams::DidCloseTextDocumentParams(const TextDocumentIdentifier &document)
+{
+ setTextDocument(document);
+}
+
+DidChangeTextDocumentParams::TextDocumentContentChangeEvent::TextDocumentContentChangeEvent(
+ const QString &text)
+{
+ setText(text);
+}
+
+DidSaveTextDocumentParams::DidSaveTextDocumentParams(const TextDocumentIdentifier &document)
+{
+ setTextDocument(document);
+}
+
+WillSaveTextDocumentParams::WillSaveTextDocumentParams(
+ const TextDocumentIdentifier &document,
+ const WillSaveTextDocumentParams::TextDocumentSaveReason &reason)
+{
+ setTextDocument(document);
+ setReason(reason);
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/textsynchronization.h b/src/shared/lsp/textsynchronization.h
new file mode 100644
index 000000000..9ec3d4164
--- /dev/null
+++ b/src/shared/lsp/textsynchronization.h
@@ -0,0 +1,215 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+#include "servercapabilities.h"
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidOpenTextDocumentParams : public JsonObject
+{
+public:
+ DidOpenTextDocumentParams() = default;
+ explicit DidOpenTextDocumentParams(const TextDocumentItem &document);
+ using JsonObject::JsonObject;
+
+ TextDocumentItem textDocument() const { return typedValue<TextDocumentItem>(textDocumentKey); }
+ void setTextDocument(TextDocumentItem textDocument) { insert(textDocumentKey, textDocument); }
+
+ bool isValid() const override { return contains(textDocumentKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidOpenTextDocumentNotification : public Notification<
+ DidOpenTextDocumentParams>
+{
+public:
+ explicit DidOpenTextDocumentNotification(const DidOpenTextDocumentParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "textDocument/didOpen";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentChangeRegistrationOptions : public JsonObject
+{
+public:
+ TextDocumentChangeRegistrationOptions();
+ explicit TextDocumentChangeRegistrationOptions(TextDocumentSyncKind kind);
+ using JsonObject::JsonObject;
+
+ TextDocumentSyncKind syncKind() const
+ { return static_cast<TextDocumentSyncKind>(typedValue<int>(syncKindKey)); }
+ void setSyncKind(TextDocumentSyncKind syncKind)
+ { insert(syncKindKey, static_cast<int>(syncKind)); }
+
+ bool isValid() const override { return contains(syncKindKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidChangeTextDocumentParams : public JsonObject
+{
+public:
+ DidChangeTextDocumentParams();
+ explicit DidChangeTextDocumentParams(const VersionedTextDocumentIdentifier &docId,
+ const QString &text = QString());
+ using JsonObject::JsonObject;
+
+ VersionedTextDocumentIdentifier textDocument() const
+ { return typedValue<VersionedTextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const VersionedTextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentContentChangeEvent : public JsonObject
+ {
+ /*
+ * An event describing a change to a text document. If range and rangeLength are omitted
+ * the new text is considered to be the full content of the document.
+ */
+ public:
+ TextDocumentContentChangeEvent() = default;
+ explicit TextDocumentContentChangeEvent(const QString &text);
+ using JsonObject::JsonObject;
+
+ // The range of the document that changed.
+ std::optional<Range> range() const { return optionalValue<Range>(rangeKey); }
+ void setRange(Range range) { insert(rangeKey, range); }
+ void clearRange() { remove(rangeKey); }
+
+ // The length of the range that got replaced.
+ std::optional<int> rangeLength() const { return optionalValue<int>(rangeLengthKey); }
+ void setRangeLength(int rangeLength) { insert(rangeLengthKey, rangeLength); }
+ void clearRangeLength() { remove(rangeLengthKey); }
+
+ // The new text of the range/document.
+ QString text() const { return typedValue<QString>(textKey); }
+ void setText(const QString &text) { insert(textKey, text); }
+
+ bool isValid() const override { return contains(textKey); }
+ };
+
+ QList<TextDocumentContentChangeEvent> contentChanges() const
+ { return array<TextDocumentContentChangeEvent>(contentChangesKey); }
+ void setContentChanges(const QList<TextDocumentContentChangeEvent> &contentChanges)
+ { insertArray(contentChangesKey, contentChanges); }
+
+ bool isValid() const override
+ { return contains(textDocumentKey) && contains(contentChangesKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidChangeTextDocumentNotification : public Notification<
+ DidChangeTextDocumentParams>
+{
+public:
+ explicit DidChangeTextDocumentNotification(const DidChangeTextDocumentParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "textDocument/didChange";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WillSaveTextDocumentParams : public JsonObject
+{
+public:
+ enum class TextDocumentSaveReason {
+ Manual = 1,
+ AfterDelay = 2,
+ FocusOut = 3
+ };
+
+ WillSaveTextDocumentParams() : WillSaveTextDocumentParams(TextDocumentIdentifier()) {}
+ explicit WillSaveTextDocumentParams(
+ const TextDocumentIdentifier &document,
+ const TextDocumentSaveReason &reason = TextDocumentSaveReason::Manual);
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ TextDocumentSaveReason reason() const
+ { return static_cast<TextDocumentSaveReason>(typedValue<int>(reasonKey)); }
+ void setReason(TextDocumentSaveReason reason) { insert(reasonKey, static_cast<int>(reason)); }
+
+ bool isValid() const override { return contains(textDocumentKey) && contains(reasonKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WillSaveTextDocumentNotification : public Notification<
+ WillSaveTextDocumentParams>
+{
+public:
+ explicit WillSaveTextDocumentNotification(const WillSaveTextDocumentParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "textDocument/willSave";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WillSaveWaitUntilTextDocumentRequest : public Request<
+ LanguageClientArray<TextEdit>, std::nullptr_t, WillSaveTextDocumentParams>
+{
+public:
+ explicit WillSaveWaitUntilTextDocumentRequest(const WillSaveTextDocumentParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/willSaveWaitUntil";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TextDocumentSaveRegistrationOptions
+ : public TextDocumentRegistrationOptions
+{
+public:
+ using TextDocumentRegistrationOptions::TextDocumentRegistrationOptions;
+
+ std::optional<bool> includeText() const { return optionalValue<bool>(includeTextKey); }
+ void setIncludeText(bool includeText) { insert(includeTextKey, includeText); }
+ void clearIncludeText() { remove(includeTextKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidSaveTextDocumentParams : public JsonObject
+{
+public:
+ DidSaveTextDocumentParams() : DidSaveTextDocumentParams(TextDocumentIdentifier()) {}
+ explicit DidSaveTextDocumentParams(const TextDocumentIdentifier &document);
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument()
+ const { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(TextDocumentIdentifier textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ std::optional<QString> text() const { return optionalValue<QString>(textKey); }
+ void setText(const QString &text) { insert(textKey, text); }
+ void clearText() { remove(textKey); }
+
+ bool isValid() const override { return contains(textDocumentKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidSaveTextDocumentNotification : public Notification<
+ DidSaveTextDocumentParams>
+{
+public:
+ explicit DidSaveTextDocumentNotification(const DidSaveTextDocumentParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "textDocument/didSave";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidCloseTextDocumentParams : public JsonObject
+{
+public:
+ DidCloseTextDocumentParams() = default;
+ explicit DidCloseTextDocumentParams(const TextDocumentIdentifier &document);
+ using JsonObject::JsonObject;
+
+ TextDocumentIdentifier textDocument() const
+ { return typedValue<TextDocumentIdentifier>(textDocumentKey); }
+ void setTextDocument(const TextDocumentIdentifier &textDocument)
+ { insert(textDocumentKey, textDocument); }
+
+ bool isValid() const override { return contains(textDocumentKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidCloseTextDocumentNotification : public Notification<
+ DidCloseTextDocumentParams>
+{
+public:
+ explicit DidCloseTextDocumentNotification(const DidCloseTextDocumentParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "textDocument/didClose";
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/lsp/textutils.cpp b/src/shared/lsp/textutils.cpp
new file mode 100644
index 000000000..1c0bff9d0
--- /dev/null
+++ b/src/shared/lsp/textutils.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "textutils.h"
+
+#include <tools/qbsassert.h>
+
+#include <QRegularExpression>
+#include <QtDebug>
+
+namespace lsp::Utils::Text {
+
+bool Position::operator==(const Position &other) const
+{
+ return line == other.line && column == other.column;
+}
+
+/*!
+ Returns the text position of a \a fileName and sets the \a postfixPos if
+ it can find a positional postfix.
+
+ The following patterns are supported: \c {filepath.txt:19},
+ \c{filepath.txt:19:12}, \c {filepath.txt+19},
+ \c {filepath.txt+19+12}, and \c {filepath.txt(19)}.
+*/
+
+Position Position::fromFileName(QStringView fileName, int &postfixPos)
+{
+ static const auto regexp = QRegularExpression("[:+](\\d+)?([:+](\\d+)?)?$");
+ // (10) MSVC-style
+ static const auto vsRegexp = QRegularExpression("[(]((\\d+)[)]?)?$");
+ const QRegularExpressionMatch match = regexp.match(fileName);
+ Position pos;
+ if (match.hasMatch()) {
+ postfixPos = match.capturedStart(0);
+ if (match.lastCapturedIndex() > 0) {
+ pos.line = match.captured(1).toInt();
+ if (match.lastCapturedIndex() > 2) // index 2 includes the + or : for the column number
+ pos.column = match.captured(3).toInt() - 1; //column is 0 based, despite line being 1 based
+ }
+ } else {
+ const QRegularExpressionMatch vsMatch = vsRegexp.match(fileName);
+ postfixPos = vsMatch.capturedStart(0);
+ if (vsMatch.lastCapturedIndex() > 1) // index 1 includes closing )
+ pos.line = vsMatch.captured(2).toInt();
+ }
+ if (pos.line > 0 && pos.column < 0)
+ pos.column = 0; // if we got a valid line make sure to return a valid TextPosition
+ return pos;
+}
+
+int Range::length(const QString &text) const
+{
+ if (end.line < begin.line)
+ return -1;
+
+ if (begin.line == end.line)
+ return end.column - begin.column;
+
+ int index = 0;
+ int currentLine = 1;
+ while (currentLine < begin.line) {
+ index = text.indexOf(QChar::LineFeed, index);
+ if (index < 0)
+ return -1;
+ ++index;
+ ++currentLine;
+ }
+ const int beginIndex = index + begin.column;
+ while (currentLine < end.line) {
+ index = text.indexOf(QChar::LineFeed, index);
+ if (index < 0)
+ return -1;
+ ++index;
+ ++currentLine;
+ }
+ return index + end.column - beginIndex;
+}
+
+bool Range::operator==(const Range &other) const
+{
+ return begin == other.begin && end == other.end;
+}
+
+QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset)
+{
+ const int lineStartUtf8Offset = currentUtf8Offset
+ ? (utf8Buffer.lastIndexOf('\n', currentUtf8Offset - 1) + 1)
+ : 0;
+ const int lineEndUtf8Offset = utf8Buffer.indexOf('\n', currentUtf8Offset);
+ return QString::fromUtf8(
+ utf8Buffer.mid(lineStartUtf8Offset, lineEndUtf8Offset - lineStartUtf8Offset));
+}
+
+static bool isByteOfMultiByteCodePoint(unsigned char byte)
+{
+ return byte & 0x80; // Check if most significant bit is set
+}
+
+bool utf8AdvanceCodePoint(const char *&current)
+{
+ if (Q_UNLIKELY(*current == '\0'))
+ return false;
+
+ // Process multi-byte UTF-8 code point (non-latin1)
+ if (Q_UNLIKELY(isByteOfMultiByteCodePoint(*current))) {
+ unsigned trailingBytesCurrentCodePoint = 1;
+ for (unsigned char c = (*current) << 2; isByteOfMultiByteCodePoint(c); c <<= 1)
+ ++trailingBytesCurrentCodePoint;
+ current += trailingBytesCurrentCodePoint + 1;
+
+ // Process single-byte UTF-8 code point (latin1)
+ } else {
+ ++current;
+ }
+
+ return true;
+}
+
+QDebug &operator<<(QDebug &stream, const Position &pos)
+{
+ stream << "line: " << pos.line << ", column: " << pos.column;
+ return stream;
+}
+
+} // namespace Utils::Text
diff --git a/src/shared/lsp/textutils.h b/src/shared/lsp/textutils.h
new file mode 100644
index 000000000..bf4ae083f
--- /dev/null
+++ b/src/shared/lsp/textutils.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QMetaType>
+#include <QString>
+
+namespace lsp::Utils::Text {
+
+class Position
+{
+public:
+ int line = 0; // 1-based
+ int column = -1; // 0-based
+
+ bool operator<(const Position &other) const
+ { return line < other.line || (line == other.line && column < other.column); }
+ bool operator==(const Position &other) const;
+
+ bool operator!=(const Position &other) const { return !(operator==(other)); }
+
+ bool isValid() const { return line > 0 && column >= 0; }
+
+ static Position fromFileName(QStringView fileName, int &postfixPos);
+};
+
+class Range
+{
+public:
+ int length(const QString &text) const;
+
+ Position begin;
+ Position end;
+
+ bool operator<(const Range &other) const { return begin < other.begin; }
+ bool operator==(const Range &other) const;
+
+ bool operator!=(const Range &other) const { return !(operator==(other)); }
+};
+
+QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset);
+
+QDebug &operator<<(QDebug &stream, const Position &pos);
+
+} // Text
diff --git a/src/shared/lsp/workspace.cpp b/src/shared/lsp/workspace.cpp
new file mode 100644
index 000000000..e653bf96b
--- /dev/null
+++ b/src/shared/lsp/workspace.cpp
@@ -0,0 +1,82 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "workspace.h"
+
+namespace lsp {
+
+constexpr const char WorkSpaceFolderRequest::methodName[];
+constexpr const char DidChangeWorkspaceFoldersNotification::methodName[];
+constexpr const char DidChangeConfigurationNotification::methodName[];
+constexpr const char ConfigurationRequest::methodName[];
+constexpr const char WorkspaceSymbolRequest::methodName[];
+constexpr const char ExecuteCommandRequest::methodName[];
+constexpr const char ApplyWorkspaceEditRequest::methodName[];
+constexpr const char DidChangeWatchedFilesNotification::methodName[];
+
+WorkSpaceFolderRequest::WorkSpaceFolderRequest()
+ : Request(methodName, nullptr)
+{ }
+
+DidChangeWorkspaceFoldersNotification::DidChangeWorkspaceFoldersNotification(
+ const DidChangeWorkspaceFoldersParams &params)
+ : Notification(methodName, params)
+{ }
+
+DidChangeConfigurationNotification::DidChangeConfigurationNotification(
+ const DidChangeConfigurationParams &params)
+ : Notification(methodName, params)
+{ }
+
+ConfigurationRequest::ConfigurationRequest(const ConfigurationParams &params)
+ : Request(methodName, params)
+{ }
+
+WorkspaceSymbolRequest::WorkspaceSymbolRequest(const WorkspaceSymbolParams &params)
+ : Request(methodName, params)
+{ }
+
+ExecuteCommandRequest::ExecuteCommandRequest(const ExecuteCommandParams &params)
+ : Request(methodName, params)
+{ }
+
+ApplyWorkspaceEditRequest::ApplyWorkspaceEditRequest(const ApplyWorkspaceEditParams &params)
+ : Request(methodName, params)
+{ }
+
+WorkspaceFoldersChangeEvent::WorkspaceFoldersChangeEvent()
+{
+ insert(addedKey, QJsonArray());
+ insert(removedKey, QJsonArray());
+}
+
+DidChangeWatchedFilesNotification::DidChangeWatchedFilesNotification(
+ const DidChangeWatchedFilesParams &params)
+ : Notification(methodName, params)
+{ }
+
+ExecuteCommandParams::ExecuteCommandParams(const Command &command)
+{
+ setCommand(command.command());
+ if (command.arguments().has_value())
+ setArguments(*command.arguments());
+}
+
+WorkSpaceFolderResult::operator const QJsonValue() const
+{
+ if (!std::holds_alternative<QList<WorkSpaceFolder>>(*this))
+ return QJsonValue::Null;
+ QJsonArray array;
+ for (const auto &folder : std::get<QList<WorkSpaceFolder>>(*this))
+ array.append(QJsonValue(folder));
+ return array;
+}
+
+std::optional<DocumentUri> ConfigurationParams::ConfigurationItem::scopeUri() const
+{
+ if (const std::optional<QString> optionalScope = optionalValue<QString>(scopeUriKey))
+ return std::make_optional(DocumentUri::fromProtocol(*optionalScope));
+ return std::nullopt;
+}
+
+} // namespace lsp
diff --git a/src/shared/lsp/workspace.h b/src/shared/lsp/workspace.h
new file mode 100644
index 000000000..ffd37c1e6
--- /dev/null
+++ b/src/shared/lsp/workspace.h
@@ -0,0 +1,245 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+
+namespace lsp {
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkSpaceFolderResult
+ : public std::variant<QList<WorkSpaceFolder>, std::nullptr_t>
+{
+public:
+ using variant::variant;
+ using variant::operator=;
+ operator const QJsonValue() const;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkSpaceFolderRequest : public Request<
+ WorkSpaceFolderResult, std::nullptr_t, std::nullptr_t>
+{
+public:
+ WorkSpaceFolderRequest();
+ using Request::Request;
+ constexpr static const char methodName[] = "workspace/workspaceFolders";
+
+ bool parametersAreValid(QString * /*errorMessage*/) const override { return true; }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceFoldersChangeEvent : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+ WorkspaceFoldersChangeEvent();
+
+ QList<WorkSpaceFolder> added() const { return array<WorkSpaceFolder>(addedKey); }
+ void setAdded(const QList<WorkSpaceFolder> &added) { insertArray(addedKey, added); }
+
+ QList<WorkSpaceFolder> removed() const { return array<WorkSpaceFolder>(removedKey); }
+ void setRemoved(const QList<WorkSpaceFolder> &removed) { insertArray(removedKey, removed); }
+
+ bool isValid() const override { return contains(addedKey) && contains(removedKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWorkspaceFoldersParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ WorkspaceFoldersChangeEvent event() const
+ { return typedValue<WorkspaceFoldersChangeEvent>(eventKey); }
+ void setEvent(const WorkspaceFoldersChangeEvent &event) { insert(eventKey, event); }
+
+ bool isValid() const override { return contains(eventKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWorkspaceFoldersNotification : public Notification<
+ DidChangeWorkspaceFoldersParams>
+{
+public:
+ explicit DidChangeWorkspaceFoldersNotification(const DidChangeWorkspaceFoldersParams &params);
+ constexpr static const char methodName[] = "workspace/didChangeWorkspaceFolders";
+ using Notification::Notification;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidChangeConfigurationParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QJsonValue settings() const { return value(settingsKey); }
+ void setSettings(QJsonValue settings) { insert(settingsKey, settings); }
+
+ bool isValid() const override { return contains(settingsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidChangeConfigurationNotification : public Notification<
+ DidChangeConfigurationParams>
+{
+public:
+ explicit DidChangeConfigurationNotification(const DidChangeConfigurationParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "workspace/didChangeConfiguration";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ConfigurationParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+ class LANGUAGESERVERPROTOCOL_EXPORT ConfigurationItem : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ std::optional<DocumentUri> scopeUri() const;
+ void setScopeUri(const DocumentUri &scopeUri) { insert(scopeUriKey, scopeUri); }
+ void clearScopeUri() { remove(scopeUriKey); }
+
+ std::optional<QString> section() const { return optionalValue<QString>(sectionKey); }
+ void setSection(const QString &section) { insert(sectionKey, section); }
+ void clearSection() { remove(sectionKey); }
+
+ bool isValid() const override { return contains(scopeUriKey); }
+ };
+
+ QList<ConfigurationItem> items() const { return array<ConfigurationItem>(itemsKey); }
+ void setItems(const QList<ConfigurationItem> &items) { insertArray(itemsKey, items); }
+
+ bool isValid() const override { return contains(itemsKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ConfigurationRequest
+ : public Request<QJsonArray, std::nullptr_t, ConfigurationParams>
+{
+public:
+ explicit ConfigurationRequest(const ConfigurationParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "workspace/configuration";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWatchedFilesParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ class FileEvent : public JsonObject
+ {
+ public:
+ using JsonObject::JsonObject;
+
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ int type() const { return typedValue<int>(typeKey); }
+ void setType(int type) { insert(typeKey, type); }
+
+ enum FileChangeType {
+ Created = 1,
+ Changed = 2,
+ Deleted = 3
+ };
+
+ bool isValid() const override { return contains(uriKey) && contains(typeKey); }
+ };
+
+ QList<FileEvent> changes() const { return array<FileEvent>(changesKey); }
+ void setChanges(const QList<FileEvent> &changes) { insertArray(changesKey, changes); }
+
+ bool isValid() const override { return contains(changesKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT DidChangeWatchedFilesNotification : public Notification<
+ DidChangeWatchedFilesParams>
+{
+public:
+ explicit DidChangeWatchedFilesNotification(const DidChangeWatchedFilesParams &params);
+ using Notification::Notification;
+ constexpr static const char methodName[] = "workspace/didChangeWatchedFiles";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString query() const { return typedValue<QString>(queryKey); }
+ void setQuery(const QString &query) { insert(queryKey, query); }
+
+ void setLimit(int limit) { insert("limit", limit); } // clangd extension
+
+ bool isValid() const override { return contains(queryKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolRequest : public Request<
+ LanguageClientArray<SymbolInformation>, std::nullptr_t, WorkspaceSymbolParams>
+{
+public:
+ explicit WorkspaceSymbolRequest(const WorkspaceSymbolParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "workspace/symbol";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandParams : public JsonObject
+{
+public:
+ explicit ExecuteCommandParams(const Command &command);
+ explicit ExecuteCommandParams(const QJsonValue &value) : JsonObject(value) {}
+ ExecuteCommandParams() : JsonObject() {}
+
+ QString command() const { return typedValue<QString>(commandKey); }
+ void setCommand(const QString &command) { insert(commandKey, command); }
+ void clearCommand() { remove(commandKey); }
+
+ std::optional<QJsonArray> arguments() const { return typedValue<QJsonArray>(argumentsKey); }
+ void setArguments(const QJsonArray &arguments) { insert(argumentsKey, arguments); }
+ void clearArguments() { remove(argumentsKey); }
+
+ bool isValid() const override { return contains(commandKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ExecuteCommandRequest : public Request<
+ QJsonValue, std::nullptr_t, ExecuteCommandParams>
+{
+public:
+ explicit ExecuteCommandRequest(const ExecuteCommandParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "workspace/executeCommand";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ std::optional<QString> label() const { return optionalValue<QString>(labelKey); }
+ void setLabel(const QString &label) { insert(labelKey, label); }
+ void clearLabel() { remove(labelKey); }
+
+ WorkspaceEdit edit() const { return typedValue<WorkspaceEdit>(editKey); }
+ void setEdit(const WorkspaceEdit &edit) { insert(editKey, edit); }
+
+ bool isValid() const override { return contains(editKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditResult : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ bool applied() const { return typedValue<bool>(appliedKey); }
+ void setApplied(bool applied) { insert(appliedKey, applied); }
+
+ bool isValid() const override { return contains(appliedKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT ApplyWorkspaceEditRequest : public Request<
+ ApplyWorkspaceEditResult, std::nullptr_t, ApplyWorkspaceEditParams>
+{
+public:
+ explicit ApplyWorkspaceEditRequest(const ApplyWorkspaceEditParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "workspace/applyEdit";
+};
+
+} // namespace LanguageClient
diff --git a/src/shared/quickjs/.clang-tidy b/src/shared/quickjs/.clang-tidy
index c5e5db244..b9209aae5 100644
--- a/src/shared/quickjs/.clang-tidy
+++ b/src/shared/quickjs/.clang-tidy
@@ -1,13 +1,3 @@
----
-Checks: >
- -bugprone-branch-clone,
- -bugprone-easily-swappable-parameters,
- -bugprone-implicit-widening-of-multiplication-result,
- -bugprone-signed-char-misuse,
- -bugprone-suspicious-memory-comparison,
- -bugprone-sizeof-expression,
- -misc-redundant-expression,
- -misc-unused-parameters
+Checks: '-*,misc-definitions-in-headers'
InheritParentConfig: true
-...
diff --git a/src/shared/quickjs/quickjs.c b/src/shared/quickjs/quickjs.c
index 578fd3a26..1e3b97d51 100644
--- a/src/shared/quickjs/quickjs.c
+++ b/src/shared/quickjs/quickjs.c
@@ -37162,6 +37162,13 @@ exception:
return JS_EXCEPTION;
}
+JSValue JS_ObjectSeal(JSContext *ctx, JSValueConst obj, int freeze)
+{
+ JSValueConst argv[] = {obj};
+ JS_FreeValue(ctx, js_object_seal(ctx, JS_UNDEFINED, 1, argv, freeze));
+ return obj;
+}
+
static JSValue js_object_fromEntries(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
@@ -42096,6 +42103,7 @@ static JSValue js___date_now(JSContext *ctx, JSValueConst this_val,
#endif
/* OS dependent: return the UTC time in microseconds since 1970. */
+// FIXME: Unused, remove?
static JSValue js___date_clock(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
@@ -45346,6 +45354,10 @@ static int js_proxy_isArray(JSContext *ctx, JSValueConst obj)
JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY);
if (!s)
return FALSE;
+ if (js_check_stack_overflow(ctx->rt, 0)) {
+ JS_ThrowStackOverflow(ctx);
+ return -1;
+ }
if (s->is_revoked) {
JS_ThrowTypeErrorRevokedProxy(ctx);
return -1;
@@ -48321,7 +48333,7 @@ static int64_t date_now(void) {
GetSystemTime(&st);
int64_t d;
SystemTimeToFileTime(&st, (FILETIME *) &d);
- return d /= 10000;
+ return (d - 116444736000000000ULL) / 10000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
@@ -54228,6 +54240,17 @@ JSValue JS_NewCFunctionMagic(JSContext *ctx, JSCFunctionMagic *func,
magic);
}
+#ifdef JS_NAN_BOXING
+JSValue mkVal(int32_t tag, int32_t val)
+{
+ return ((uint64_t)(tag) << 32) | (uint32_t)(val);
+}
+
+JSValue mkPtr(int32_t tag, void *p)
+{
+ return ((uint64_t)(tag) << 32) | (uintptr_t)(p);
+}
+#else
JSValue mkVal(int32_t tag, int32_t val)
{
return (JSValue){ (JSValueUnion){ .int32 = val }, tag };
@@ -54237,6 +54260,7 @@ JSValue mkPtr(int32_t tag, void *p)
{
return (JSValue){ (JSValueUnion){ .ptr = p }, tag };
}
+#endif
void JS_FreeValue(JSContext *ctx, JSValue v) {
if (JS_VALUE_HAS_REF_COUNT(v)) {
diff --git a/src/shared/quickjs/quickjs.h b/src/shared/quickjs/quickjs.h
index fbea4af3d..aa4a74c9c 100644
--- a/src/shared/quickjs/quickjs.h
+++ b/src/shared/quickjs/quickjs.h
@@ -156,8 +156,9 @@ static inline double JS_VALUE_GET_FLOAT64(JSValue v)
#define JS_NAN (0x7ff8000000000000 - ((uint64_t)JS_FLOAT64_TAG_ADDEND << 32))
-static inline JSValue __JS_NewFloat64(JSContext *ctx, double d)
+static inline JSValue JS_NewFloat64Impl(JSContext *ctx, double d)
{
+ (void) ctx;
union {
double d;
uint64_t u64;
@@ -707,6 +708,8 @@ int JS_DeleteProperty(JSContext *ctx, JSValueConst obj, JSAtom prop, int flags);
int JS_SetPrototype(JSContext *ctx, JSValueConst obj, JSValueConst proto_val);
JSValue JS_GetPrototype(JSContext *ctx, JSValueConst val);
+JSValue JS_ObjectSeal(JSContext *ctx, JSValueConst obj, int freeze);
+
#define JS_GPN_STRING_MASK (1 << 0)
#define JS_GPN_SYMBOL_MASK (1 << 1)
#define JS_GPN_PRIVATE_MASK (1 << 2)
diff --git a/src/src.qbs b/src/src.qbs
index 84c045f43..f5a5ae5a1 100644
--- a/src/src.qbs
+++ b/src/src.qbs
@@ -7,6 +7,7 @@ Project {
"plugins/plugins.qbs",
"shared/bundledqt/bundledqt.qbs",
"shared/json/json.qbs",
+ "shared/lsp/lsp.qbs",
"shared/quickjs/quickjs.qbs",
"shared/variant/variant.qbs",
]
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3a868fbac..1ff088061 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,3 +1,5 @@
add_subdirectory(auto)
add_subdirectory(benchmarker)
+add_subdirectory(clang-format-test)
add_subdirectory(fuzzy-test)
+add_subdirectory(lspclient)
diff --git a/tests/auto/api/testdata/infinite-loop-scanning-scan/file.in b/tests/auto/api/testdata/infinite-loop-scanning-scan/file.in
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/api/testdata/infinite-loop-scanning-scan/file.in
diff --git a/tests/auto/api/testdata/infinite-loop-scanning-scan/infinite-loop.qbs b/tests/auto/api/testdata/infinite-loop-scanning-scan/infinite-loop.qbs
new file mode 100644
index 000000000..5e3e33b6e
--- /dev/null
+++ b/tests/auto/api/testdata/infinite-loop-scanning-scan/infinite-loop.qbs
@@ -0,0 +1,21 @@
+Product {
+ type: "t"
+ Depends { name: "m" }
+ Group {
+ files: "file.in"
+ fileTags: "i"
+ }
+ Rule {
+ inputs: "i"
+ Artifact {
+ filePath: "dummy"
+ fileTags: "t"
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.silent = true;
+ cmd.sourceCode = function() {};
+ return cmd;
+ }
+ }
+}
diff --git a/tests/auto/api/testdata/infinite-loop-scanning-scan/modules/m/m.qbs b/tests/auto/api/testdata/infinite-loop-scanning-scan/modules/m/m.qbs
new file mode 100644
index 000000000..9c8a18072
--- /dev/null
+++ b/tests/auto/api/testdata/infinite-loop-scanning-scan/modules/m/m.qbs
@@ -0,0 +1,6 @@
+Module {
+ Scanner {
+ inputs: "i"
+ scan: { while (true); }
+ }
+} \ No newline at end of file
diff --git a/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/file.in b/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/file.in
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/file.in
diff --git a/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/infinite-loop.qbs b/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/infinite-loop.qbs
new file mode 100644
index 000000000..5e3e33b6e
--- /dev/null
+++ b/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/infinite-loop.qbs
@@ -0,0 +1,21 @@
+Product {
+ type: "t"
+ Depends { name: "m" }
+ Group {
+ files: "file.in"
+ fileTags: "i"
+ }
+ Rule {
+ inputs: "i"
+ Artifact {
+ filePath: "dummy"
+ fileTags: "t"
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.silent = true;
+ cmd.sourceCode = function() {};
+ return cmd;
+ }
+ }
+}
diff --git a/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/modules/m/m.qbs b/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/modules/m/m.qbs
new file mode 100644
index 000000000..0476478b9
--- /dev/null
+++ b/tests/auto/api/testdata/infinite-loop-scanning-searchpaths/modules/m/m.qbs
@@ -0,0 +1,7 @@
+Module {
+ Scanner {
+ inputs: "i"
+ searchPaths: { while (true); }
+ scan: []
+ }
+}
diff --git a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs
index 4ba829a5b..0c86d05c6 100644
--- a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs
+++ b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs
@@ -40,6 +40,11 @@ Project {
name: "static2"
files: [ "static2.cpp", "static2.h" ]
Depends { name: "cpp" }
+ Probe {
+ id: tcPrinter
+ property bool isGcc: qbs.toolchain.contains("gcc")
+ configure: { console.info("is gcc: " + isGcc); }
+ }
}
}
diff --git a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs
index 1bcc2876f..c30cf40f9 100644
--- a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs
+++ b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs
@@ -16,7 +16,11 @@ Project {
id: osCheck
property bool isNormalUnix: qbs.targetOS.includes("unix")
&& !qbs.targetOS.includes("darwin")
- configure: { console.info("is normal unix: " + (isNormalUnix ? "yes" : "no")); }
+ property bool isGcc: qbs.toolchain.contains("gcc")
+ configure: {
+ console.info("is normal unix: " + (isNormalUnix ? "yes" : "no"));
+ console.info("is gcc: " + isGcc);
+ }
}
}
diff --git a/tests/auto/api/testdata/restored-warnings/restored-warnings.qbs b/tests/auto/api/testdata/restored-warnings/restored-warnings.qbs
index bbdfbeadb..9d4abb757 100644
--- a/tests/auto/api/testdata/restored-warnings/restored-warnings.qbs
+++ b/tests/auto/api/testdata/restored-warnings/restored-warnings.qbs
@@ -1,14 +1,25 @@
import qbs.Process 1.5
-CppApplication {
- name: "theProduct"
+Project {
+ CppApplication {
+ name: "theProduct"
- property bool moreFiles: false
- cpp.blubb: true
+ cpp.blubb: true
- files: ["file.cpp", "main.cpp"]
- Group {
- condition: moreFiles
- files: ["blubb.cpp"]
+ files: ["file.cpp", "main.cpp"]
+ }
+
+ Product {
+ name: "theOtherProduct"
+ property bool dummy: { throw "this one comes from a thread"; }
+ }
+
+ Product {
+ name: "aThirdProduct"
+ property bool moreFiles: false
+ Group {
+ condition: moreFiles
+ files: ["blubb.txt"]
+ }
}
}
diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp
index 94d02c15c..3816eca2b 100644
--- a/tests/auto/api/tst_api.cpp
+++ b/tests/auto/api/tst_api.cpp
@@ -928,16 +928,12 @@ void TestApi::dependencyOnMultiplexedType()
} else {
QVERIFY(p.name() == "p2");
++p2Count;
-
- // FIXME: This is an odd effect of our current algorithm: We collect the products
- // matching the requested type and add Depends items with their names ("p1" in
- // this case). Later, the algorithm checking for compatibility regarding the
- // multiplexing axes picks the aggregate. However, the aggregate does not have
- // a matching type... It's not entirely clear what the real expected
- // result should be here.
- QCOMPARE(p.dependencies().size(), 2);
+ QVERIFY(p.dependencies().contains("dep"));
}
}
+ QCOMPARE(depCount, 1);
+ QCOMPARE(p1Count, 3);
+ QCOMPARE(p2Count, 1);
std::unique_ptr<qbs::BuildJob> buildJob(project.buildAllProducts(qbs::BuildOptions()));
waitForFinished(buildJob.get());
QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString()));
@@ -1426,6 +1422,9 @@ void TestApi::infiniteLoopBuilding_data()
QTest::addColumn<QString>("projectDirName");
QTest::newRow("JS Command") << QString("infinite-loop-js");
QTest::newRow("Process Command") << QString("infinite-loop-process");
+ QTest::newRow("Scanner (scan property)") << QString("infinite-loop-scanning-scan");
+ QTest::newRow("Scanner (searchPaths property)")
+ << QString("infinite-loop-scanning-searchpaths");
}
void TestApi::infiniteLoopResolving()
@@ -1554,27 +1553,30 @@ void TestApi::linkDynamicAndStaticLibs()
BuildDescriptionReceiver bdr;
qbs::BuildOptions options;
options.setEchoMode(qbs::CommandEchoModeCommandLine);
+ m_logSink->output.clear();
const qbs::ErrorInfo errorInfo = doBuildProject("link-dynamiclibs-staticlibs", &bdr, nullptr,
nullptr, options);
VERIFY_NO_ERROR(errorInfo);
+ const bool isGcc = m_logSink->output.contains("is gcc: true");
+ const bool isNotGcc = m_logSink->output.contains("is gcc: false");
+ if (isNotGcc)
+ QSKIP("The remainder of this test applies only to GCC");
+ QVERIFY(isGcc);
+
// The dependent static libs should not appear in the link command for the executable.
- const SettingsPtr s = settings();
- const qbs::Profile buildProfile(profileName(), s.get());
- if (profileToolchain(buildProfile).contains("gcc")) {
- static const std::regex appLinkCmdRex(" -o [^ ]*/HelloWorld" QBS_HOST_EXE_SUFFIX " ");
- QString appLinkCmd;
- for (const QString &line : std::as_const(bdr.descriptionLines)) {
- const auto ln = line.toStdString();
- if (std::regex_search(ln, appLinkCmdRex)) {
- appLinkCmd = line;
- break;
- }
+ static const std::regex appLinkCmdRex(" -o [^ ]*/HelloWorld" QBS_HOST_EXE_SUFFIX " ");
+ QString appLinkCmd;
+ for (const QString &line : std::as_const(bdr.descriptionLines)) {
+ const auto ln = line.toStdString();
+ if (std::regex_search(ln, appLinkCmdRex)) {
+ appLinkCmd = line;
+ break;
}
- QVERIFY(!appLinkCmd.isEmpty());
- QVERIFY(!appLinkCmd.contains("static1"));
- QVERIFY(!appLinkCmd.contains("static2"));
}
+ QVERIFY(!appLinkCmd.isEmpty());
+ QVERIFY(!appLinkCmd.contains("static1"));
+ QVERIFY(!appLinkCmd.contains("static2"));
}
void TestApi::linkStaticAndDynamicLibs()
@@ -1589,31 +1591,32 @@ void TestApi::linkStaticAndDynamicLibs()
const bool isNormalUnix = m_logSink->output.contains("is normal unix: yes");
const bool isNotNormalUnix = m_logSink->output.contains("is normal unix: no");
QVERIFY2(isNormalUnix != isNotNormalUnix, qPrintable(m_logSink->output));
+ const bool isGcc = m_logSink->output.contains("is gcc: true");
+ const bool isNotGcc = m_logSink->output.contains("is gcc: false");
+ if (isNotGcc)
+ QSKIP("The remainder of this test applies only to GCC");
+ QVERIFY(isGcc);
// The dependencies libdynamic1.so and libstatic2.a must not appear in the link command for the
// executable. The -rpath-link line for libdynamic1.so must be there.
- const SettingsPtr s = settings();
- const qbs::Profile buildProfile(profileName(), s.get());
- if (profileToolchain(buildProfile).contains("gcc")) {
- static const std::regex appLinkCmdRex(" -o [^ ]*/HelloWorld" QBS_HOST_EXE_SUFFIX " ");
- QString appLinkCmd;
- for (const QString &line : std::as_const(bdr.descriptionLines)) {
- const auto ln = line.toStdString();
- if (std::regex_search(ln, appLinkCmdRex)) {
- appLinkCmd = line;
- break;
- }
- }
- QVERIFY(!appLinkCmd.isEmpty());
- if (isNormalUnix) {
- const std::regex rpathLinkRex("-rpath-link=\\S*/"
- + relativeProductBuildDir("dynamic2").toStdString());
- const auto ln = appLinkCmd.toStdString();
- QVERIFY(std::regex_search(ln, rpathLinkRex));
+ static const std::regex appLinkCmdRex(" -o [^ ]*/HelloWorld" QBS_HOST_EXE_SUFFIX " ");
+ QString appLinkCmd;
+ for (const QString &line : std::as_const(bdr.descriptionLines)) {
+ const auto ln = line.toStdString();
+ if (std::regex_search(ln, appLinkCmdRex)) {
+ appLinkCmd = line;
+ break;
}
- QVERIFY(!appLinkCmd.contains("libstatic2.a"));
- QVERIFY(!appLinkCmd.contains("libdynamic2.so"));
}
+ QVERIFY(!appLinkCmd.isEmpty());
+ if (isNormalUnix) {
+ const std::regex rpathLinkRex("-rpath-link=\\S*/"
+ + relativeProductBuildDir("dynamic2").toStdString());
+ const auto ln = appLinkCmd.toStdString();
+ QVERIFY(std::regex_search(ln, rpathLinkRex));
+ }
+ QVERIFY(!appLinkCmd.contains("libstatic2.a"));
+ QVERIFY(!appLinkCmd.contains("libdynamic2.so"));
}
void TestApi::listBuildSystemFiles()
@@ -1922,7 +1925,8 @@ struct ProductDataSelector
bool qbsPropertiesMatch(const qbs::ProductData &p) const
{
for (auto it = qbsProperties.begin(); it != qbsProperties.end(); ++it) {
- if (it.value() != p.moduleProperties().getModuleProperty("qbs", it.key()))
+ if (!qbs::qVariantsEqual(
+ it.value(), p.moduleProperties().getModuleProperty("qbs", it.key())))
return false;
}
return true;
@@ -2217,7 +2221,7 @@ void TestApi::newPatternMatch()
void TestApi::nonexistingProjectPropertyFromProduct()
{
qbs::SetupProjectParameters setupParams
- = defaultSetupParameters("nonexistingprojectproperties");
+ = defaultSetupParameters("nonexistingprojectproperties/invalidaccessfromproduct.qbs");
std::unique_ptr<qbs::SetupProjectJob> job(qbs::Project().setupProject(setupParams,
m_logSink, nullptr));
waitForFinished(job.get());
@@ -2526,6 +2530,7 @@ qbs::SetupProjectParameters TestApi::defaultSetupParameters(const QString &proje
setupParams.setLibexecPath(QDir::cleanPath(QCoreApplication::applicationDirPath()
+ QLatin1String("/" QBS_RELATIVE_LIBEXEC_PATH)));
setupParams.setTopLevelProfile(profileName());
+ setupParams.setMaxJobCount(2);
setupParams.setConfigurationName(QStringLiteral("default"));
setupParams.setSettingsDirectory(settings()->baseDirectory());
return setupParams;
@@ -2710,12 +2715,14 @@ void TestApi::restoredWarnings()
waitForFinished(job.get());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
job.reset(nullptr);
- QCOMPARE(toSet(m_logSink->warnings).size(), 3);
+ QCOMPARE(toSet(m_logSink->warnings).size(), 5);
const auto beforeErrors = m_logSink->warnings;
for (const qbs::ErrorInfo &e : beforeErrors) {
const QString msg = e.toString();
QVERIFY2(msg.contains("Superfluous version")
|| msg.contains("Property 'blubb' is not declared")
+ || msg.contains("this one comes from a thread")
+ || msg.contains("Product 'theOtherProduct' had errors and was disabled")
|| msg.contains("Product 'theProduct' had errors and was disabled"),
qPrintable(msg));
}
@@ -2726,26 +2733,29 @@ void TestApi::restoredWarnings()
waitForFinished(job.get());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
job.reset(nullptr);
- QCOMPARE(toSet(m_logSink->warnings).size(), 3);
+ QCOMPARE(toSet(m_logSink->warnings).size(), 5);
m_logSink->warnings.clear();
// Re-resolving with changes: Errors come from the re-resolving, stored ones must be suppressed.
QVariantMap overridenValues;
- overridenValues.insert("products.theProduct.moreFiles", true);
+ overridenValues.insert("products.aThirdProduct.moreFiles", true);
setupParams.setOverriddenValues(overridenValues);
job.reset(qbs::Project().setupProject(setupParams, m_logSink, nullptr));
waitForFinished(job.get());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
job.reset(nullptr);
- QCOMPARE(toSet(m_logSink->warnings).size(), 4); // One more for the additional group
+ QCOMPARE(toSet(m_logSink->warnings).size(), 6); // One more for the additional group
const auto afterErrors = m_logSink->warnings;
for (const qbs::ErrorInfo &e : afterErrors) {
const QString msg = e.toString();
- QVERIFY2(msg.contains("Superfluous version")
- || msg.contains("Property 'blubb' is not declared")
- || msg.contains("blubb.cpp' does not exist")
- || msg.contains("Product 'theProduct' had errors and was disabled"),
- qPrintable(msg));
+ QVERIFY2(
+ msg.contains("Superfluous version") || msg.contains("Property 'blubb' is not declared")
+ || msg.contains("blubb.txt' does not exist")
+ || msg.contains("this one comes from a thread")
+ || msg.contains("Product 'theOtherProduct' had errors and was disabled")
+ || msg.contains("Product 'theThirdProduct' had errors and was disabled")
+ || msg.contains("Product 'theProduct' had errors and was disabled"),
+ qPrintable(msg));
}
m_logSink->warnings.clear();
}
diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs
index bf6b3d219..b042d180a 100644
--- a/tests/auto/auto.qbs
+++ b/tests/auto/auto.qbs
@@ -11,6 +11,7 @@ Project {
"blackbox/blackbox-joblimits.qbs",
"blackbox/blackbox-providers.qbs",
"blackbox/blackbox-qt.qbs",
+ "blackbox/blackbox-tutorial.qbs",
"blackbox/blackbox-windows.qbs",
"blackbox/blackbox.qbs",
"buildgraph/buildgraph.qbs",
diff --git a/tests/auto/blackbox/CMakeLists.txt b/tests/auto/blackbox/CMakeLists.txt
index 1d90ec95b..88e19acdf 100644
--- a/tests/auto/blackbox/CMakeLists.txt
+++ b/tests/auto/blackbox/CMakeLists.txt
@@ -80,6 +80,15 @@ add_qbs_test(blackbox-qt
tst_blackboxqt.h
)
+add_qbs_test(blackbox-tutorial
+ SOURCES
+ ../shared.h
+ tst_blackboxbase.cpp
+ tst_blackboxbase.h
+ tst_blackboxtutorial.h
+ tst_blackboxtutorial.cpp
+ )
+
add_qbs_test(blackbox-windows
SOURCES
../shared.h
diff --git a/tests/auto/blackbox/blackbox-tutorial.qbs b/tests/auto/blackbox/blackbox-tutorial.qbs
new file mode 100644
index 000000000..174821ffa
--- /dev/null
+++ b/tests/auto/blackbox/blackbox-tutorial.qbs
@@ -0,0 +1,21 @@
+import qbs.Utilities
+
+QbsAutotest {
+ testName: "blackbox-tutorial"
+ Depends { name: "qbs_app" }
+ Depends { name: "qbs-setup-toolchains" }
+ Group {
+ name: "testdata"
+ prefix: "../../../tutorial/"
+ files: ["**/*"]
+ fileTags: []
+ }
+ files: [
+ "../shared.h",
+ "tst_blackboxtutorial.cpp",
+ "tst_blackboxtutorial.h",
+ "tst_blackboxbase.cpp",
+ "tst_blackboxbase.h",
+ ]
+ cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)])
+}
diff --git a/tests/auto/blackbox/find/find-xcode.qbs b/tests/auto/blackbox/find/find-xcode.qbs
index bb6ee9718..15c2aa17e 100644
--- a/tests/auto/blackbox/find/find-xcode.qbs
+++ b/tests/auto/blackbox/find/find-xcode.qbs
@@ -14,7 +14,9 @@ Product {
cmd.description = output.filePath;
cmd.sourceCode = function() {
var tools = {};
- if (product.moduleProperty("xcode", "present")) {
+ var present = product.moduleProperty("xcode", "present");
+ tools["present"] = !!present;
+ if (present) {
var keys = [
"developerPath",
"version"
diff --git a/tests/auto/blackbox/testdata-apple/codesign/codesign.qbs b/tests/auto/blackbox/testdata-apple/codesign/codesign.qbs
index 08c8f730b..c1fc0502a 100644
--- a/tests/auto/blackbox/testdata-apple/codesign/codesign.qbs
+++ b/tests/auto/blackbox/testdata-apple/codesign/codesign.qbs
@@ -2,10 +2,13 @@ import "../multiarch-helpers.js" as Helpers
Project {
name: "p"
+ // we do not have the access to xcode version in qbs.architectures so we need to pass it here
property string xcodeVersion
property bool isBundle: true
property bool enableSigning: true
+ property bool multiArch: false
+ property bool multiVariant: false
CppApplication {
name: "A"
@@ -18,7 +21,8 @@ Project {
installDir: ""
qbs.architectures:
- project.xcodeVersion ? Helpers.getArchitectures(qbs, project.xcodeVersion) : []
+ multiArch ? Helpers.getArchitectures(qbs, project.xcodeVersion) : []
+ qbs.buildVariants: project.multiVariant ? ["debug", "release"] : []
}
DynamicLibrary {
@@ -32,7 +36,8 @@ Project {
install: true
installDir: ""
qbs.architectures:
- project.xcodeVersion ? Helpers.getArchitectures(qbs, project.xcodeVersion) : []
+ multiArch ? Helpers.getArchitectures(qbs, project.xcodeVersion) : []
+ qbs.buildVariants: project.multiVariant ? ["debug", "release"] : []
}
LoadableModule {
@@ -46,6 +51,7 @@ Project {
install: true
installDir: ""
qbs.architectures:
- project.xcodeVersion ? Helpers.getArchitectures(qbs, project.xcodeVersion) : []
+ multiArch ? Helpers.getArchitectures(qbs, project.xcodeVersion) : []
+ qbs.buildVariants: project.multiVariant ? ["debug", "release"] : []
}
}
diff --git a/tests/auto/blackbox/testdata-providers/allowed-values/allowed-values.qbs b/tests/auto/blackbox/testdata-providers/allowed-values/allowed-values.qbs
new file mode 100644
index 000000000..e6dbaddde
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/allowed-values/allowed-values.qbs
@@ -0,0 +1,4 @@
+Product {
+ Depends { name: "qbsmetatestmodule" }
+ qbsModuleProviders: "provider"
+}
diff --git a/tests/auto/blackbox/testdata-providers/allowed-values/module-providers/provider.qbs b/tests/auto/blackbox/testdata-providers/allowed-values/module-providers/provider.qbs
new file mode 100644
index 000000000..c0e426c96
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/allowed-values/module-providers/provider.qbs
@@ -0,0 +1,14 @@
+import "../../qbs-module-providers-helpers.js" as Helpers
+
+ModuleProvider {
+ isEager: false
+ property stringList aProperty: "zero"
+ PropertyOptions {
+ name: "aProperty"
+ allowedValues: ["one", "two"]
+ }
+ relativeSearchPaths: {
+ Helpers.writeModule(outputBaseDir, moduleName, "from_provider");
+ return "";
+ }
+}
diff --git a/tests/auto/blackbox/testdata-providers/broken-provider/broken-provider.qbs b/tests/auto/blackbox/testdata-providers/broken-provider/broken-provider.qbs
new file mode 100644
index 000000000..461c7e30e
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/broken-provider/broken-provider.qbs
@@ -0,0 +1,13 @@
+Project {
+ qbsModuleProviders: "provider_a"
+ name: "project"
+ Project {
+ name: "innerProject"
+ Product {
+ name: "p1"
+ Depends { name: "qbsothermodule"; required: false }
+ Depends { name: "qbsmetatestmodule" }
+ }
+ }
+
+}
diff --git a/tests/auto/blackbox/testdata-providers/broken-provider/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/broken-provider/module-providers/provider_a.qbs
new file mode 100644
index 000000000..f446d2d13
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/broken-provider/module-providers/provider_a.qbs
@@ -0,0 +1,5 @@
+ModuleProvider {
+ relativeSearchPaths: {
+ throw "This provider is broken";
+ }
+}
diff --git a/tests/auto/blackbox/testdata-providers/module-providers/module-providers.qbs b/tests/auto/blackbox/testdata-providers/module-providers/module-providers.qbs
index e83c80bb1..89bd1a11d 100644
--- a/tests/auto/blackbox/testdata-providers/module-providers/module-providers.qbs
+++ b/tests/auto/blackbox/testdata-providers/module-providers/module-providers.qbs
@@ -10,10 +10,11 @@ Project {
readonly property string beginning: "beginning"
CppApplication {
name: "app1"
+ property string chooseLettersFrom: project.beginning // This indirection tests QBS-1747.
Depends { name: "mygenerator.module1" }
Depends { name: "mygenerator.module2" }
Depends { name: "othergenerator" }
- moduleProviders.mygenerator.chooseLettersFrom: project.beginning
+ moduleProviders.mygenerator.chooseLettersFrom: chooseLettersFrom
moduleProviders.othergenerator.someDefines: name
files: "main.cpp"
}
diff --git a/tests/auto/blackbox/testdata-providers/non-eager-provider/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/non-eager-provider/module-providers/provider_a.qbs
new file mode 100644
index 000000000..6cd9177db
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/non-eager-provider/module-providers/provider_a.qbs
@@ -0,0 +1,11 @@
+import "../../qbs-module-providers-helpers.js" as Helpers
+
+ModuleProvider {
+ isEager: false
+ relativeSearchPaths: {
+ if (moduleName === "nonexistentmodule")
+ return undefined;
+ Helpers.writeModule(outputBaseDir, moduleName, "from_provider_a");
+ return "";
+ }
+}
diff --git a/tests/auto/blackbox/testdata-providers/non-eager-provider/non-eager-provider.qbs b/tests/auto/blackbox/testdata-providers/non-eager-provider/non-eager-provider.qbs
new file mode 100644
index 000000000..bd3662de3
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/non-eager-provider/non-eager-provider.qbs
@@ -0,0 +1,13 @@
+Project {
+ Product {
+ name: "p1"
+ Depends { name: "qbsmetatestmodule" }
+ Depends { name: "qbsothermodule" }
+ Depends { name: "nonexistentmodule"; required: false }
+ property bool dummy: {
+ console.info("p1.qbsmetatestmodule.prop: " + qbsmetatestmodule.prop);
+ console.info("p1.qbsothermodule.prop: " + qbsothermodule.prop);
+ }
+ qbsModuleProviders: "provider_a"
+ }
+}
diff --git a/tests/auto/blackbox/testdata-providers/probe-in-module-provider/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/probe-in-module-provider/module-providers/provider_a.qbs
index 8a7c7d6ed..476a83143 100644
--- a/tests/auto/blackbox/testdata-providers/probe-in-module-provider/module-providers/provider_a.qbs
+++ b/tests/auto/blackbox/testdata-providers/probe-in-module-provider/module-providers/provider_a.qbs
@@ -1,16 +1,23 @@
import "../../qbs-module-providers-helpers.js" as Helpers
ModuleProvider {
+ property string sysroot: qbs.sysroot
Probe {
id: theProbe
+ property string theValue: "value"
+ property string dummy: sysroot
configure: {
- console.info("Running probe");
+ console.info("Running probe with irrelevant value '" + dummy + "'");
found = true;
}
}
+ isEager: false
property bool found: theProbe.found
+ property string theValue: theProbe.theValue
relativeSearchPaths: {
- Helpers.writeModule(outputBaseDir, "qbsmetatestmodule", undefined, undefined, found);
+ Helpers.writeModule(outputBaseDir, "qbsmetatestmodule", theValue, undefined, found);
+ if (sysroot !== qbs.sysroot)
+ throw "this is unexpected";
return "";
}
}
diff --git a/tests/auto/blackbox/testdata-providers/probe-in-module-provider/probe-in-module-provider.qbs b/tests/auto/blackbox/testdata-providers/probe-in-module-provider/probe-in-module-provider.qbs
index cb346beeb..1f2b3d387 100644
--- a/tests/auto/blackbox/testdata-providers/probe-in-module-provider/probe-in-module-provider.qbs
+++ b/tests/auto/blackbox/testdata-providers/probe-in-module-provider/probe-in-module-provider.qbs
@@ -4,5 +4,6 @@ Product {
Depends { name: "qbsmetatestmodule" }
property bool dummy: {
console.info("p.qbsmetatestmodule.boolProp: " + JSON.stringify(qbsmetatestmodule.boolProp));
+ console.info("p.qbsmetatestmodule.prop: " + JSON.stringify(qbsmetatestmodule.prop));
}
}
diff --git a/tests/auto/blackbox/testdata-providers/removal-version/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/removal-version/module-providers/provider_a.qbs
new file mode 100644
index 000000000..7f358acbc
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/removal-version/module-providers/provider_a.qbs
@@ -0,0 +1,14 @@
+import "../../qbs-module-providers-helpers.js" as Helpers
+
+ModuleProvider {
+ isEager: false
+ property bool deprecated: false
+ PropertyOptions {
+ name: "deprecated"
+ removalVersion: "2.2.0"
+ }
+ relativeSearchPaths: {
+ Helpers.writeModule(outputBaseDir, moduleName, "from_provider_a");
+ return "";
+ }
+}
diff --git a/tests/auto/blackbox/testdata-providers/removal-version/removal-version.qbs b/tests/auto/blackbox/testdata-providers/removal-version/removal-version.qbs
new file mode 100644
index 000000000..1aa5e2ce9
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/removal-version/removal-version.qbs
@@ -0,0 +1,12 @@
+Project {
+ qbsModuleProviders: "provider_a"
+ name: "project"
+ Project {
+ name: "innerProject"
+ Product {
+ name: "p1"
+ Depends { name: "qbsmetatestmodule" }
+ }
+ }
+
+}
diff --git a/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qbs b/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qbs
new file mode 100644
index 000000000..4b82bc405
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qbs
@@ -0,0 +1,22 @@
+import qbs.Utilities
+
+Product {
+ condition: {
+ var ok = Utilities.versionCompare(Qt.core.version, "5.0.0") >= 0;
+ if (!ok)
+ console.info("Qt is too old");
+ return ok;
+ }
+ name: "QDoc Test"
+ type: ["qdoc-html", "qch"]
+
+ Depends { name: "Qt.core" }
+
+ files: ["qdoc.qdoc"]
+
+ Group {
+ name: "main qdocconf file"
+ files: "qdoc.qdocconf"
+ fileTags: "qdocconf-main"
+ }
+}
diff --git a/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qdoc b/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qdoc
new file mode 100644
index 000000000..9e44e0948
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qdoc
@@ -0,0 +1,7 @@
+/*!
+ \page index.html
+
+ \title QDoc Test
+
+ QDoc Test is a test for QDoc.
+*/ \ No newline at end of file
diff --git a/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qdocconf b/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qdocconf
new file mode 100644
index 000000000..8c82bb575
--- /dev/null
+++ b/tests/auto/blackbox/testdata-qt/qdoc/qdoc.qdocconf
@@ -0,0 +1,13 @@
+project = QDoc Test
+description = QDoc Test
+
+headerdirs = .
+sourcedirs = .
+exampledirs = .
+
+outputdir = doc/html
+
+qhp.projects = QDocTest
+qhp.QDocTest.file = qdoctest.qhp
+qhp.QDocTest.namespace = org.qt-project.QDocTest
+qhp.QDocTest.virtualFolder = doc
diff --git a/tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs b/tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs
index 8176a7c3e..0885e6b0b 100644
--- a/tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs
+++ b/tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs
@@ -4,4 +4,9 @@ QtApplication {
Depends { name: "Qt.quick" }
Qt.quick.qmlDebugging: true
files: "main.cpp"
+ Probe {
+ id: checker
+ property bool isGcc: qbs.toolchain.contains("gcc")
+ configure: { console.info("is gcc: " + isGcc); }
+ }
}
diff --git a/tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs b/tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs
index f48bf8d1f..b4fb0df4d 100644
--- a/tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs
+++ b/tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs
@@ -10,4 +10,9 @@ CppApplication {
cpp.linkerFlags: ["-s"]
}
files: ["main.cpp"]
+ Probe {
+ id: checker
+ property bool isUnixGcc: qbs.toolchain.contains("gcc") && !qbs.targetOS.contains("macos")
+ configure: { console.info("is gcc: " + isUnixGcc); }
+ }
}
diff --git a/tests/auto/blackbox/testdata/exports-cmake/Foo.cpp b/tests/auto/blackbox/testdata/exports-cmake/Foo.cpp
new file mode 100644
index 000000000..ea334f9af
--- /dev/null
+++ b/tests/auto/blackbox/testdata/exports-cmake/Foo.cpp
@@ -0,0 +1,5 @@
+#include "Foo.h"
+int someFooWork()
+{
+ return 42;
+}
diff --git a/tests/auto/blackbox/testdata/exports-cmake/Foo.h b/tests/auto/blackbox/testdata/exports-cmake/Foo.h
new file mode 100644
index 000000000..2f279f577
--- /dev/null
+++ b/tests/auto/blackbox/testdata/exports-cmake/Foo.h
@@ -0,0 +1,16 @@
+#ifndef FOO_H
+#define FOO_H
+#include <dllexport.h>
+
+#ifdef FOO_LIB_STATIC
+#define FOO_LIB_EXPORT
+#else
+#ifdef FOO_LIB
+#define FOO_LIB_EXPORT DLL_EXPORT
+#else
+#define FOO_LIB_EXPORT DLL_IMPORT
+#endif
+#endif
+
+FOO_LIB_EXPORT int someFooWork();
+#endif // FOO_H
diff --git a/tests/auto/blackbox/testdata/exports-cmake/cmake/CMakeLists.txt b/tests/auto/blackbox/testdata/exports-cmake/cmake/CMakeLists.txt
new file mode 100644
index 000000000..d874e0e92
--- /dev/null
+++ b/tests/auto/blackbox/testdata/exports-cmake/cmake/CMakeLists.txt
@@ -0,0 +1,7 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(qbs_import)
+
+find_package(Bar PATHS REQUIRED)
+add_executable(Consumer main.cpp)
+target_link_libraries(Consumer Bar)
diff --git a/tests/auto/blackbox/testdata/exports-cmake/cmake/main.cpp b/tests/auto/blackbox/testdata/exports-cmake/cmake/main.cpp
new file mode 100644
index 000000000..1a1fa90ec
--- /dev/null
+++ b/tests/auto/blackbox/testdata/exports-cmake/cmake/main.cpp
@@ -0,0 +1,6 @@
+#include <Foo.h>
+
+int main()
+{
+ return someFooWork();
+}
diff --git a/tests/auto/blackbox/testdata/exports-cmake/exports-cmake.qbs b/tests/auto/blackbox/testdata/exports-cmake/exports-cmake.qbs
new file mode 100644
index 000000000..6464af705
--- /dev/null
+++ b/tests/auto/blackbox/testdata/exports-cmake/exports-cmake.qbs
@@ -0,0 +1,70 @@
+import qbs.FileInfo
+
+Project {
+ property bool isStatic: false
+ property bool isBundle: false
+
+ property string headersInstallDir: "include"
+
+ Product {
+ name: "DllExport"
+ Depends { name: "Exporter.cmake" }
+ Group {
+ name: "API headers"
+ files: ["../dllexport.h"]
+ qbs.install: true
+ qbs.installDir: project.headersInstallDir
+ }
+ Group {
+ fileTagsFilter: ["Exporter.cmake.package"]
+ qbs.install: true
+ qbs.installDir: "/lib/cmake/DllExport"
+ }
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: FileInfo.joinPaths(
+ exportingProduct.qbs.installRoot,
+ exportingProduct.qbs.installPrefix,
+ project.headersInstallDir)
+ }
+ }
+
+ Library {
+ type: project.isStatic ? "staticlibrary" : "dynamiclibrary"
+ Depends { name: "cpp" }
+ Depends { name: "DllExport" }
+ Depends { name: "Exporter.cmake" }
+ Exporter.cmake.packageName: "Bar"
+ name: "Foo"
+ files: ["Foo.cpp"]
+ version: "1.2.3"
+ cpp.includePaths: "."
+ cpp.defines: "FOO_LIB"
+ Group {
+ name: "API headers"
+ files: ["Foo.h"]
+ qbs.install: true
+ qbs.installDir: project.headersInstallDir
+ }
+ install: true
+ installImportLib: true
+ Group {
+ fileTagsFilter: ["Exporter.cmake.package"]
+ qbs.install: true
+ qbs.installDir: "/lib/cmake/Bar"
+ }
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: FileInfo.joinPaths(
+ exportingProduct.qbs.installRoot,
+ exportingProduct.qbs.installPrefix,
+ project.headersInstallDir)
+ cpp.defines: ["FOO=1"].concat(project.isStatic ? ["FOO_LIB_STATIC"] : [])
+ cpp.commonCompilerFlags: "-DOTHER_DEF=1"
+ cpp.linkerFlags: exportingProduct.qbs.toolchain.contains("gcc") ? ["-s"] : []
+ }
+
+ Depends { name: 'bundle' }
+ bundle.isBundle: qbs.targetOS.includes("darwin") && project.isBundle
+ }
+}
diff --git a/tests/auto/blackbox/testdata/exports-cmake/find-cmake.qbs b/tests/auto/blackbox/testdata/exports-cmake/find-cmake.qbs
new file mode 100644
index 000000000..52f388966
--- /dev/null
+++ b/tests/auto/blackbox/testdata/exports-cmake/find-cmake.qbs
@@ -0,0 +1,46 @@
+import qbs.Probes
+
+Product {
+ Depends { name: "cpp" }
+
+ Probes.BinaryProbe {
+ id: cmakeProbe
+ names: "cmake"
+ }
+
+ Probes.BinaryProbe {
+ id: ninjaProbe
+ names: ["ninja"]
+ }
+
+ property bool test: {
+ var data = {
+ cmakeFound: cmakeProbe.found,
+ cmakeFilePath: cmakeProbe.filePath,
+ crossCompiling: qbs.targetPlatform !== qbs.hostPlatform,
+ installPrefix: qbs.installPrefix
+ };
+ data.buildEnv = {}
+ Object.assign(data.buildEnv, cpp.buildEnv); // deep copy buildEnv from a probe
+ if (qbs.toolchain.includes("gcc")) {
+ data.buildEnv["CC"] = cpp.cCompilerName;
+ data.buildEnv["CXX"] = cpp.cxxCompilerName;
+ } else {
+ data.buildEnv["CC"] = cpp.compilerName;
+ data.buildEnv["CXX"] = cpp.compilerName;
+ }
+
+ if (ninjaProbe.found) {
+ data.generator = "Ninja";
+ } else {
+ if (qbs.toolchain.includes("msvc")) {
+ data.generator = "NMake Makefiles"
+ } else if (qbs.toolchain.includes("mingw")) {
+ data.generator = "MinGW Makefiles";
+ } else if (qbs.toolchain.includes("gcc")) {
+ data.generator = "Unix Makefiles";
+ }
+ }
+ console.info("---" + JSON.stringify(data) + "---");
+ }
+}
diff --git a/tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs b/tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs
index 1d1d2d54e..47275821f 100644
--- a/tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs
+++ b/tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs
@@ -14,6 +14,9 @@ Project {
name: "helper3"
required: false
}
+ Depends { name: "helper7" }
+ helper7.fileName: "helper7.c"
+
type: ["diamond"]
files: [
diff --git a/tests/auto/blackbox/testdata/groups-in-modules/imports/Helper7Base.qbs b/tests/auto/blackbox/testdata/groups-in-modules/imports/Helper7Base.qbs
new file mode 100644
index 000000000..cc55e6351
--- /dev/null
+++ b/tests/auto/blackbox/testdata/groups-in-modules/imports/Helper7Base.qbs
@@ -0,0 +1,8 @@
+Module {
+ property string directory
+ property string fileName
+ Group {
+ prefix: directory + "/"
+ files: fileName
+ }
+}
diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper7/helper7.c b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper7/helper7.c
new file mode 100644
index 000000000..a83f5476e
--- /dev/null
+++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper7/helper7.c
@@ -0,0 +1 @@
+void helper7(void) {}
diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper7/helper7.qbs b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper7/helper7.qbs
new file mode 100644
index 000000000..90e1f11e2
--- /dev/null
+++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper7/helper7.qbs
@@ -0,0 +1,3 @@
+Helper7Base {
+ directory: path
+}
diff --git a/tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs b/tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs
index dd2184c7e..e36ae47ca 100644
--- a/tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs
+++ b/tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs
@@ -12,10 +12,11 @@ CppApplication {
Depends { name: "cpp" }
cpp.cxxLanguageVersion: "c++17"
- cpp.minimumMacosVersion: "10.8"
+ cpp.minimumMacosVersion: "10.15"
cpp.warningLevel: "none"
Depends { name: "protobuf.cpp"; required: false }
+ Depends { name: "grpc++"; id: grpcpp; required: false }
protobuf.cpp.useGrpc: true
property bool hasDependencies: {
diff --git a/tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs b/tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs
index 1e7426744..5824fe518 100644
--- a/tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs
+++ b/tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs
@@ -36,6 +36,9 @@ Product {
destination.write(source.atEof() ? [ 0xFF ] : [ 0x00 ]);
source.close();
destination.close();
+ source = new BinaryFile("destination.dat", BinaryFile.ReadOnly);
+ destination = new BinaryFile("destination2.dat", BinaryFile.WriteOnly);
+ destination.write(source.read(8));
};
commands.push(cmd);
return commands;
diff --git a/tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs b/tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs
index e69cde064..c41e8f1d7 100644
--- a/tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs
+++ b/tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs
@@ -6,6 +6,11 @@ Project {
files: ["lib1.cpp"]
Depends { name: "bundle" }
bundle.isBundle: false
+ Probe {
+ id: checker
+ property bool isGcc: qbs.toolchain.contains("gcc")
+ configure: { console.info("is gcc: " + isGcc); }
+ }
}
DynamicLibrary {
diff --git a/tests/auto/blackbox/testdata/linkerMode/main.s b/tests/auto/blackbox/testdata/linkerMode/main.s
index ef88f8c52..fb165114b 100644
--- a/tests/auto/blackbox/testdata/linkerMode/main.s
+++ b/tests/auto/blackbox/testdata/linkerMode/main.s
@@ -3,3 +3,4 @@
_main:
main:
+ ret
diff --git a/tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs b/tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs
index 0b4de0ab9..6d068b6a2 100644
--- a/tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs
+++ b/tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs
@@ -55,6 +55,13 @@ DynamicLibrary {
}
}
+ Probe {
+ id: checker
+ property bool isGcc: qbs.toolchain.contains("gcc")
+ property bool isLinux: qbs.targetOS.contains("linux")
+ configure: { console.info("is Linux gcc: " + (isGcc && isLinux)) }
+ }
+
qbs.installPrefix: ""
install: true
installDir: ""
diff --git a/tests/auto/blackbox/testdata/lsp/lsp.qbs b/tests/auto/blackbox/testdata/lsp/lsp.qbs
new file mode 100644
index 000000000..24479e0ec
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/lsp.qbs
@@ -0,0 +1,11 @@
+Project {
+ Product {
+ name: "dep"
+ Depends { name: "m" }
+ Depends { name: "Prefix"; submodules: ["m1", "m2", "m3"] }
+
+ }
+ Product {
+ Depends { name: "dep" }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/lsp/modules/Prefix/m1/m1.qbs b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m1/m1.qbs
new file mode 100644
index 000000000..09bac2dc2
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m1/m1.qbs
@@ -0,0 +1,5 @@
+Module {
+ property bool p1
+ property string p2
+ property bool x
+}
diff --git a/tests/auto/language/testdata/erroneous/modules/prefix1/prefix1.qbs b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m2/m2.qbs
index 84957060c..84957060c 100644
--- a/tests/auto/language/testdata/erroneous/modules/prefix1/prefix1.qbs
+++ b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m2/m2.qbs
diff --git a/tests/auto/blackbox/testdata/lsp/modules/Prefix/m3/m3.qbs b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m3/m3.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/modules/Prefix/m3/m3.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/blackbox/testdata/lsp/modules/m/m.qbs b/tests/auto/blackbox/testdata/lsp/modules/m/m.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/blackbox/testdata/lsp/modules/m/m.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/blackbox/testdata/msvc-asm-flags/include/header.inc b/tests/auto/blackbox/testdata/msvc-asm-flags/include/header.inc
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/msvc-asm-flags/include/header.inc
diff --git a/tests/auto/blackbox/testdata/msvc-asm-flags/msvc-asm-flags.asm b/tests/auto/blackbox/testdata/msvc-asm-flags/msvc-asm-flags.asm
new file mode 100644
index 000000000..eddc49131
--- /dev/null
+++ b/tests/auto/blackbox/testdata/msvc-asm-flags/msvc-asm-flags.asm
@@ -0,0 +1,8 @@
+include header.inc
+
+.code
+main proc
+ mov ecx, 16
+main endp
+
+end \ No newline at end of file
diff --git a/tests/auto/blackbox/testdata/msvc-asm-flags/msvc-asm-flags.qbs b/tests/auto/blackbox/testdata/msvc-asm-flags/msvc-asm-flags.qbs
new file mode 100644
index 000000000..d632098c9
--- /dev/null
+++ b/tests/auto/blackbox/testdata/msvc-asm-flags/msvc-asm-flags.qbs
@@ -0,0 +1,6 @@
+StaticLibrary {
+ condition: qbs.toolchain.includes("msvc")
+ Depends { name: "cpp" }
+ files: "msvc-asm-flags.asm"
+ cpp.assemblerFlags: ["/I", "include"]
+}
diff --git a/tests/auto/blackbox/testdata/path-list-in-probe/main.cpp b/tests/auto/blackbox/testdata/path-list-in-probe/main.cpp
new file mode 100644
index 000000000..5bc549337
--- /dev/null
+++ b/tests/auto/blackbox/testdata/path-list-in-probe/main.cpp
@@ -0,0 +1,4 @@
+int main()
+{
+ return 0;
+} \ No newline at end of file
diff --git a/tests/auto/blackbox/testdata/path-list-in-probe/path-list-in-probe.qbs b/tests/auto/blackbox/testdata/path-list-in-probe/path-list-in-probe.qbs
new file mode 100644
index 000000000..3bf36367a
--- /dev/null
+++ b/tests/auto/blackbox/testdata/path-list-in-probe/path-list-in-probe.qbs
@@ -0,0 +1,18 @@
+Project {
+ CppApplication {
+ Probe {
+ id: theProbe
+ property pathList result
+ configure: {
+ result = ["main.cpp"]
+ found = true
+ }
+ }
+ property pathList res: theProbe.found ? theProbe.result : []
+
+ Group {
+ name: "files"
+ files: res
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs b/tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs
index 30b7f4ace..752673b78 100644
--- a/tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs
+++ b/tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs
@@ -11,8 +11,8 @@ Project {
Depends { name: "plugin2" } // not to be linked
Depends {
name: "plugin3" // supposed to be linked
- //property bool theCondition: true
- cpp.link: /*theCondition && */product.name === "myapp" // TODO: Make this work
+ property bool theCondition: true
+ cpp.link: theCondition && product.name === "myapp"
}
Depends { name: "plugin4" } // supposed to be linked
Depends { name: "helper1" } // supposed to be linked
diff --git a/tests/auto/blackbox/testdata/property-assignment-on-non-present-module/property-assignment-on-non-present-module.qbs b/tests/auto/blackbox/testdata/property-assignment-on-non-present-module/property-assignment-on-non-present-module.qbs
deleted file mode 100644
index a01d6c561..000000000
--- a/tests/auto/blackbox/testdata/property-assignment-on-non-present-module/property-assignment-on-non-present-module.qbs
+++ /dev/null
@@ -1,4 +0,0 @@
-Product {
- Depends { name: "nein"; required: false }
- nein.doch: "ohhh!"
-}
diff --git a/tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs b/tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs
index cf63eb17f..959552ea5 100644
--- a/tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs
+++ b/tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs
@@ -11,8 +11,7 @@ CppApplication {
consoleApplication: true
Depends { name: "cpp" }
- cpp.cxxLanguageVersion: "c++11"
- cpp.minimumMacosVersion: "10.8"
+ cpp.minimumMacosVersion: "10.15"
Depends { name: "protobuf.cpp"; required: false }
property bool hasProtobuf: {
diff --git a/tests/auto/blackbox/testdata/protobuf/addressbook_nanopb.qbs b/tests/auto/blackbox/testdata/protobuf/addressbook_nanopb.qbs
index ccb724ef5..3dfc911e1 100644
--- a/tests/auto/blackbox/testdata/protobuf/addressbook_nanopb.qbs
+++ b/tests/auto/blackbox/testdata/protobuf/addressbook_nanopb.qbs
@@ -11,7 +11,6 @@ CppApplication {
consoleApplication: true
Depends { name: "cpp" }
- cpp.cxxLanguageVersion: "c++11"
cpp.minimumMacosVersion: "10.8"
Depends { name: "protobuf.nanopb"; required: false }
diff --git a/tests/auto/blackbox/testdata/protobuf/create-proto-library.qbs b/tests/auto/blackbox/testdata/protobuf/create-proto-library.qbs
index 967bf2bb8..005752fd8 100644
--- a/tests/auto/blackbox/testdata/protobuf/create-proto-library.qbs
+++ b/tests/auto/blackbox/testdata/protobuf/create-proto-library.qbs
@@ -11,7 +11,6 @@ Project {
name: "proto_lib"
Depends { name: "cpp" }
- cpp.cxxLanguageVersion: "c++11"
cpp.minimumMacosVersion: "10.8"
protobuf.cpp.importPaths: product.sourceDirectory
diff --git a/tests/auto/blackbox/testdata/protobuf/import.qbs b/tests/auto/blackbox/testdata/protobuf/import.qbs
index 41669020d..56d489de8 100644
--- a/tests/auto/blackbox/testdata/protobuf/import.qbs
+++ b/tests/auto/blackbox/testdata/protobuf/import.qbs
@@ -12,7 +12,6 @@ CppApplication {
protobuf.cpp.importPaths: [sourceDirectory]
- cpp.cxxLanguageVersion: "c++11"
cpp.minimumMacosVersion: "10.8"
Depends { name: "protobuf.cpp"; required: false }
diff --git a/tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs b/tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs
index 143e9c20f..07be566cb 100644
--- a/tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs
+++ b/tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs
@@ -13,7 +13,6 @@ CppApplication {
property path theImportDir
protobuf.cpp.importPaths: (theImportDir ? [theImportDir] : []).concat([sourceDirectory])
- cpp.cxxLanguageVersion: "c++11"
cpp.minimumMacosVersion: "10.8"
Depends { name: "protobuf.cpp"; required: false }
diff --git a/tests/auto/blackbox/testdata/qbs-config-import-export/config.json b/tests/auto/blackbox/testdata/qbs-config-import-export/config.json
new file mode 100644
index 000000000..edcaf238a
--- /dev/null
+++ b/tests/auto/blackbox/testdata/qbs-config-import-export/config.json
@@ -0,0 +1,11 @@
+{
+ "group": {
+ "key1": "value1",
+ "key2": "value2"
+ },
+ "key": "value",
+ "listKey": [
+ "valueOne",
+ "valueTwo"
+ ]
+}
diff --git a/tests/auto/blackbox/testdata/qbs-config-import-export/config.txt b/tests/auto/blackbox/testdata/qbs-config-import-export/config.txt
new file mode 100644
index 000000000..2bd19c422
--- /dev/null
+++ b/tests/auto/blackbox/testdata/qbs-config-import-export/config.txt
@@ -0,0 +1,4 @@
+group.key1: "value1"
+group.key2: "value2"
+key: "value"
+listKey: ["valueOne", "valueTwo"]
diff --git a/tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs b/tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs
index f7ed8e61a..fabdf48db 100644
--- a/tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs
+++ b/tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs
@@ -2,4 +2,9 @@ CppApplication {
name: "the product"
files: ["file1.cpp", "file2.cpp", "main.cpp"]
cpp.cxxFlags: ["-flto"]
+ Probe {
+ id: checker
+ property bool isGcc: qbs.toolchain.contains("gcc") && !qbs.toolchain.contains("clang")
+ configure: { console.info("is gcc: " + isGcc); }
+ }
}
diff --git a/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication-lib.cpp b/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication-lib.cpp
new file mode 100644
index 000000000..6418df94d
--- /dev/null
+++ b/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication-lib.cpp
@@ -0,0 +1,3 @@
+int dynamicFunc() {
+ return 1;
+} \ No newline at end of file
diff --git a/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication-main.cpp b/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication-main.cpp
new file mode 100644
index 000000000..60f8494f6
--- /dev/null
+++ b/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication-main.cpp
@@ -0,0 +1,5 @@
+extern int dynamicFunc();
+
+int main() {
+ return dynamicFunc();
+} \ No newline at end of file
diff --git a/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication.qbs b/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication.qbs
new file mode 100644
index 000000000..adb63872a
--- /dev/null
+++ b/tests/auto/blackbox/testdata/rpathlink-deduplication/rpathlink-deduplication.qbs
@@ -0,0 +1,47 @@
+Project {
+ DynamicLibrary {
+ Depends { name: "bundle" }
+ Depends { name: "cpp" }
+
+ bundle.isBundle: false
+ name: "DynamicLibraryA"
+ files: ["rpathlink-deduplication-lib.cpp"]
+ }
+
+ DynamicLibrary {
+ Depends { name: "bundle" }
+ Depends { name: "cpp" }
+ Depends { name: "DynamicLibraryA" }
+
+ bundle.isBundle: false
+ name: "DynamicLibraryB"
+ files: ["rpathlink-deduplication-lib.cpp"]
+ }
+
+ DynamicLibrary {
+ Depends { name: "bundle" }
+ Depends { name: "cpp" }
+ Depends { name: "DynamicLibraryA" }
+
+ bundle.isBundle: false
+ name: "DynamicLibraryC"
+ files: ["rpathlink-deduplication-lib.cpp"]
+ }
+
+ CppApplication {
+ Depends { name: "bundle" }
+ Depends { name: "DynamicLibraryB" }
+ Depends { name: "DynamicLibraryC" }
+ consoleApplication: true
+ bundle.isBundle: false
+ cpp.removeDuplicateLibraries: false
+ files: "rpathlink-deduplication-main.cpp"
+ property bool test: {
+ if (cpp.useRPathLink)
+ console.info("useRPathLink: true");
+ else
+ console.info("useRPathLink: false");
+ console.info("===" + cpp.rpathLinkFlag + "===");
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/auto/blackbox/testdata/run-multiplexed/main.cpp b/tests/auto/blackbox/testdata/run-multiplexed/main.cpp
new file mode 100644
index 000000000..237c8ce18
--- /dev/null
+++ b/tests/auto/blackbox/testdata/run-multiplexed/main.cpp
@@ -0,0 +1 @@
+int main() {}
diff --git a/tests/auto/blackbox/testdata/run-multiplexed/run-multiplexed.qbs b/tests/auto/blackbox/testdata/run-multiplexed/run-multiplexed.qbs
new file mode 100644
index 000000000..11577b54a
--- /dev/null
+++ b/tests/auto/blackbox/testdata/run-multiplexed/run-multiplexed.qbs
@@ -0,0 +1,21 @@
+import qbs.Host
+
+CppApplication {
+ aggregate: false
+ consoleApplication: true
+ name: "app"
+ multiplexByQbsProperties: "buildVariants"
+
+ qbs.buildVariants: ["debug", "release"]
+
+ files: "main.cpp"
+
+ Probe {
+ id: checker
+ property string targetPlatform: qbs.targetPlatform
+ configure: {
+ if (targetPlatform !== Host.platform())
+ console.info("targetPlatform differs from hostPlatform");
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs b/tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs
index 9f57b7f01..4198b863f 100644
--- a/tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs
+++ b/tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs
@@ -8,10 +8,13 @@ Project {
Probe {
id: osProbe
property stringList targetOS: qbs.targetOS
+ property stringList toolchain: qbs.toolchain
configure: {
console.info("is windows: " + (targetOS.includes("windows") ? "yes" : "no"));
console.info("is macos: " + (targetOS.includes("macos") ? "yes" : "no"));
console.info("is darwin: " + (targetOS.includes("darwin") ? "yes" : "no"));
+ console.info("is gcc: " + (toolchain.includes("gcc") ? "yes" : "no"));
+ console.info("is msvc: " + (toolchain.includes("msvc") ? "yes" : "no"));
}
}
}
diff --git a/tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs b/tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs
index 6e2137173..35dd7a00f 100644
--- a/tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs
+++ b/tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs
@@ -20,4 +20,9 @@ Project {
cpp.rpaths: qbs.installRoot + "/lib"
cpp.systemRunPaths: project.setRunPaths ? [qbs.installRoot + "/lib"] : []
}
+ Probe {
+ id: checker
+ property bool isUnix: qbs.targetOS.contains("unix")
+ configure: { console.info("is unix: " + isUnix); }
+ }
}
diff --git a/tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs b/tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs
index 534f49ff2..0e28e5687 100644
--- a/tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs
+++ b/tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs
@@ -11,4 +11,9 @@ CppApplication {
name: "file that needs help from the environment to find a header"
files: "including.cpp"
}
+ Probe {
+ id: checker
+ property bool isGcc: qbs.toolchain.contains("gcc")
+ configure: { console.info("is gcc: " + isGcc); }
+ }
}
diff --git a/tests/auto/blackbox/testdata/versionscript/versionscript.qbs b/tests/auto/blackbox/testdata/versionscript/versionscript.qbs
index cc5c7b1cc..fcb4314a2 100644
--- a/tests/auto/blackbox/testdata/versionscript/versionscript.qbs
+++ b/tests/auto/blackbox/testdata/versionscript/versionscript.qbs
@@ -20,6 +20,12 @@ DynamicLibrary {
return [cmd];
}
}
+ Probe {
+ id: checker
+ property bool isLinux: qbs.targetOS.includes("linux")
+ property bool isGcc: qbs.toolchain.contains("gcc")
+ configure: { console.info("is gcc for Linux: " + (isLinux && isGcc)); }
+ }
qbs.installPrefix: ""
install: true
diff --git a/tests/auto/blackbox/testdata/wildcards-and-change-tracking/nonrecursive/subdir1/file.txt b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/nonrecursive/subdir1/file.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/nonrecursive/subdir1/file.txt
diff --git a/tests/auto/blackbox/testdata/wildcards-and-change-tracking/nonrecursive/subdir2/file.txt b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/nonrecursive/subdir2/file.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/nonrecursive/subdir2/file.txt
diff --git a/tests/auto/blackbox/testdata/wildcards-and-change-tracking/recursive1/recursive.txt b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/recursive1/recursive.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/recursive1/recursive.txt
diff --git a/tests/auto/blackbox/testdata/wildcards-and-change-tracking/recursive2/recursive.txt b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/recursive2/recursive.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/recursive2/recursive.txt
diff --git a/tests/auto/blackbox/testdata/wildcards-and-change-tracking/wildcards-and-change-tracking.qbs b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/wildcards-and-change-tracking.qbs
new file mode 100644
index 000000000..0a889bab3
--- /dev/null
+++ b/tests/auto/blackbox/testdata/wildcards-and-change-tracking/wildcards-and-change-tracking.qbs
@@ -0,0 +1,16 @@
+Product {
+ Group {
+ name: "recursive"
+ files: "**/file.txt"
+ }
+ Group {
+ name: "directories"
+ prefix: "nonrecursive/"
+ files: "subdi?/file.txt"
+ }
+ Group {
+ prefix: "nonrecursive/empty/"
+ name: "no files"
+ files: "*.txt"
+ }
+}
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index 424d08642..c62fdbca0 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -63,7 +63,6 @@
#define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir)
using qbs::Internal::HostOsInfo;
-using qbs::Profile;
class MacosTarHealer {
public:
@@ -139,7 +138,7 @@ QString TestBlackbox::findArchiver(const QString &fileName, int *status)
QString binary = findExecutable(QStringList(fileName));
if (binary.isEmpty()) {
const SettingsPtr s = settings();
- Profile p(profileName(), s.get());
+ qbs::Profile p(profileName(), s.get());
binary = findExecutable(p.value("archiver.command").toStringList());
}
return binary;
@@ -299,7 +298,7 @@ void TestBlackbox::textTemplate()
static QStringList sortedFileList(const QByteArray &ba)
{
- auto list = QString::fromUtf8(ba).split(QRegularExpression("[\r\n]"), QBS_SKIP_EMPTY_PARTS);
+ auto list = QString::fromUtf8(ba).split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts);
std::sort(list.begin(), list.end());
return list;
}
@@ -699,7 +698,7 @@ void TestBlackbox::buildDirectories()
QDir::setCurrent(projectDir);
QCOMPARE(runQbs(), 0);
const QStringList outputLines
- = QString::fromLocal8Bit(m_qbsStdout.trimmed()).split('\n', QBS_SKIP_EMPTY_PARTS);
+ = QString::fromLocal8Bit(m_qbsStdout.trimmed()).split('\n', Qt::SkipEmptyParts);
QVERIFY2(outputLines.contains(projectDir + '/' + relativeProductBuildDir("p1")),
m_qbsStdout.constData());
QVERIFY2(outputLines.contains(projectDir + '/' + relativeProductBuildDir("p2")),
@@ -1387,9 +1386,9 @@ void TestBlackbox::variantSuffix_data()
std::make_pair(QString("unix"), QStringList())});
}
-static bool waitForProcessSuccess(QProcess &p)
+static bool waitForProcessSuccess(QProcess &p, int msecs = 30000)
{
- if (!p.waitForStarted() || !p.waitForFinished()) {
+ if (!p.waitForStarted(msecs) || !p.waitForFinished(msecs)) {
qDebug() << p.errorString();
return false;
}
@@ -1594,14 +1593,14 @@ void TestBlackbox::versionCheck_data()
void TestBlackbox::versionScript()
{
- const SettingsPtr s = settings();
- Profile buildProfile(profileName(), s.get());
- QStringList toolchain = profileToolchain(buildProfile);
- if (!toolchain.contains("gcc") || targetOs() != HostOsInfo::HostOsLinux)
- QSKIP("version script test only applies to Linux");
QDir::setCurrent(testDataDir + "/versionscript");
- QCOMPARE(runQbs(QbsRunParameters(QStringList("-q")
- << ("qbs.installRoot:" + QDir::currentPath()))), 0);
+ QCOMPARE(runQbs(QbsRunParameters("resolve", {"qbs.installRoot:" + QDir::currentPath()})), 0);
+ const bool isLinuxGcc = m_qbsStdout.contains("is gcc for Linux: true");
+ const bool isNotLinuxGcc = m_qbsStdout.contains("is gcc for Linux: false");
+ if (isNotLinuxGcc)
+ QSKIP("version script test only applies to Linux");
+ QVERIFY(isLinuxGcc);
+ QCOMPARE(runQbs(QbsRunParameters(QStringList("-q"))), 0);
const QString output = QString::fromLocal8Bit(m_qbsStderr);
const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*---(.*)---.*"),
QRegularExpression::DotMatchesEverythingOption);
@@ -2068,10 +2067,13 @@ void TestBlackbox::separateDebugInfo()
const bool isDarwin = m_qbsStdout.contains("is darwin: yes");
const bool isNotDarwin = m_qbsStdout.contains("is darwin: no");
QVERIFY(isDarwin != isNotDarwin);
+ const bool isGcc = m_qbsStdout.contains("is gcc: yes");
+ const bool isNotGcc = m_qbsStdout.contains("is gcc: no");
+ QVERIFY(isGcc != isNotGcc);
+ const bool isMsvc = m_qbsStdout.contains("is msvc: yes");
+ const bool isNotMsvc = m_qbsStdout.contains("is msvc: no");
+ QVERIFY(isMsvc != isNotMsvc);
- const SettingsPtr s = settings();
- Profile buildProfile(profileName(), s.get());
- QStringList toolchain = profileToolchain(buildProfile);
if (isDarwin) {
QVERIFY(directoryExists(relativeProductBuildDir("app1") + "/app1.app.dSYM"));
QVERIFY(regularFileExists(relativeProductBuildDir("app1")
@@ -2151,7 +2153,7 @@ void TestBlackbox::separateDebugInfo()
+ "/bar4.bundle.dSYM/Contents/Resources/DWARF")
.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1);
QVERIFY(regularFileExists(relativeProductBuildDir("bar5") + "/bar5.bundle.dwarf"));
- } else if (toolchain.contains("gcc")) {
+ } else if (isGcc) {
const QString exeSuffix = isWindows ? ".exe" : "";
const QString dllPrefix = isWindows ? "" : "lib";
const QString dllSuffix = isWindows ? ".dll" : ".so";
@@ -2165,7 +2167,7 @@ void TestBlackbox::separateDebugInfo()
+ '/' + dllPrefix + "bar1" + dllSuffix + ".debug"));
QVERIFY(!QFile::exists(relativeProductBuildDir("bar2")
+ '/' + dllPrefix + "bar2" + dllSuffix + ".debug"));
- } else if (toolchain.contains("msvc")) {
+ } else if (isMsvc) {
QVERIFY(QFile::exists(relativeProductBuildDir("app1") + "/app1.pdb"));
QVERIFY(QFile::exists(relativeProductBuildDir("foo1") + "/foo1.pdb"));
QVERIFY(QFile::exists(relativeProductBuildDir("bar1") + "/bar1.pdb"));
@@ -2222,6 +2224,9 @@ void TestBlackbox::trackExternalProductChanges()
QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp"));
QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp"));
+ const bool isGcc = m_qbsStdout.contains("is gcc: true");
+ const bool isNotGcc = m_qbsStdout.contains("is gcc: false");
+
QbsRunParameters params;
params.environment.insert("QBS_TEST_PULL_IN_FILE_VIA_ENV", "1");
QCOMPARE(runQbs(params), 0);
@@ -2271,12 +2276,11 @@ void TestBlackbox::trackExternalProductChanges()
QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp"));
QVERIFY(m_qbsStdout.contains("compiling fileExists.cpp"));
+ if (isNotGcc)
+ QSKIP("The remainder of this test requires a GCC-like toolchain");
+ QVERIFY(isGcc);
+
rmDirR(relativeBuildDir());
- const SettingsPtr s = settings();
- const Profile profile(profileName(), s.get());
- const QStringList toolchainTypes = profileToolchain(profile);
- if (!toolchainTypes.contains("gcc"))
- QSKIP("Need GCC-like compiler to run this test");
params.environment = QbsRunParameters::defaultEnvironment();
params.environment.insert("INCLUDE_PATH_TEST", "1");
params.expectFailure = true;
@@ -2569,22 +2573,21 @@ void TestBlackbox::removeDuplicateLibraries()
void TestBlackbox::reproducibleBuild()
{
- const SettingsPtr s = settings();
- const Profile profile(profileName(), s.get());
- const QStringList toolchains = profileToolchain(profile);
- if (!toolchains.contains("gcc"))
- QSKIP("reproducible builds only supported for gcc");
- if (toolchains.contains("clang"))
- QSKIP("reproducible builds are not supported for clang");
-
QFETCH(bool, reproducible);
QDir::setCurrent(testDataDir + "/reproducible-build");
- QbsRunParameters params;
+ QbsRunParameters params("resolve");
params.arguments << QString("modules.cpp.enableReproducibleBuilds:")
+ (reproducible ? "true" : "false");
rmDirR(relativeBuildDir());
QCOMPARE(runQbs(params), 0);
+ const bool isGcc = m_qbsStdout.contains("is gcc: true");
+ const bool isNotGcc = m_qbsStdout.contains("is gcc: false");
+ if (isNotGcc)
+ QSKIP("reproducible builds only supported for gcc");
+ QVERIFY(isGcc);
+
+ QCOMPARE(runQbs(), 0);
QFile object(relativeProductBuildDir("the product") + '/' + inputDirHash(".") + '/'
+ objectFileName("file1.cpp", profileName()));
QVERIFY2(object.open(QIODevice::ReadOnly), qPrintable(object.fileName()));
@@ -2592,6 +2595,7 @@ void TestBlackbox::reproducibleBuild()
object.close();
QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
QVERIFY(!object.exists());
+ params.command = "build";
QCOMPARE(runQbs(params), 0);
if (reproducible) {
QVERIFY(object.open(QIODevice::ReadOnly));
@@ -2653,6 +2657,32 @@ void TestBlackbox::retaggedOutputArtifact()
QVERIFY2(!QFile::exists(a3), qPrintable(a3));
}
+void TestBlackbox::rpathlinkDeduplication()
+{
+ QDir::setCurrent(testDataDir + "/rpathlink-deduplication");
+ QbsRunParameters resolveParams{"resolve"};
+ QCOMPARE(runQbs(resolveParams), 0);
+ const bool useRPathLink = m_qbsStdout.contains("useRPathLink: true");
+ const bool dontUseRPathLink = m_qbsStdout.contains("useRPathLink: false");
+ QVERIFY2(useRPathLink || dontUseRPathLink, m_qbsStdout);
+ if (dontUseRPathLink)
+ QSKIP("Only applies to toolchains that support rPathLink");
+ const QString output = QString::fromLocal8Bit(m_qbsStdout);
+ const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*===(.*)===.*"),
+ QRegularExpression::DotMatchesEverythingOption);
+ const QRegularExpressionMatch match = pattern.match(output);
+ QVERIFY2(match.hasMatch(), qPrintable(output));
+ QCOMPARE(pattern.captureCount(), 1);
+ const QString linkFlag = match.captured(1);
+
+ QbsRunParameters buildParams;
+ buildParams.arguments = QStringList({"--command-echo-mode", "command-line"});
+ QCOMPARE(runQbs(buildParams), 0);
+ // private DynamicLibraryA is a dependency for 2 other libs but should only appear once
+ const auto libDir = QFileInfo(relativeProductBuildDir("DynamicLibraryA")).absoluteFilePath();
+ QCOMPARE(m_qbsStdout.count((linkFlag + libDir).toUtf8()), 1);
+}
+
void TestBlackbox::ruleConditions()
{
QDir::setCurrent(testDataDir + "/ruleConditions");
@@ -2737,6 +2767,25 @@ void TestBlackbox::ruleWithNonRequiredInputs()
QVERIFY2(m_qbsStdout.contains("generating"), m_qbsStdout.constData());
}
+void TestBlackbox::runMultiplexed()
+{
+ QDir::setCurrent(testDataDir + "/run-multiplexed");
+ QCOMPARE(runQbs({"resolve"}), 0);
+ if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
+ QSKIP("Cannot run binaries in cross-compiled build");
+
+ QbsRunParameters params("run");
+ params.expectFailure = true;
+ QVERIFY(runQbs(params) != 0);
+ params.arguments = QStringList{"-p", "app"};
+ QVERIFY(runQbs(params) != 0);
+ params.expectFailure = false;
+ params.arguments.last() = "app {\"buildVariant\":\"debug\"}";
+ QCOMPARE(runQbs(params), 0);
+ params.arguments.last() = "app {\"buildVariant\":\"release\"}";
+ QCOMPARE(runQbs(params), 0);
+}
+
void TestBlackbox::sanitizer_data()
{
QTest::addColumn<QString>("sanitizer");
@@ -3141,6 +3190,12 @@ void TestBlackbox::pathProbe()
QVERIFY2(m_qbsStderr.contains("Probe failed to find files"), m_qbsStderr);
}
+void TestBlackbox::pathListInProbe()
+{
+ QDir::setCurrent(testDataDir + "/path-list-in-probe");
+ QCOMPARE(runQbs(), 0);
+}
+
void TestBlackbox::pchChangeTracking()
{
QDir::setCurrent(testDataDir + "/pch-change-tracking");
@@ -3457,13 +3512,6 @@ void TestBlackbox::productProperties()
QVERIFY(regularFileExists(relativeExecutableFilePath("blubb_user")));
}
-void TestBlackbox::propertyAssignmentOnNonPresentModule()
-{
- QDir::setCurrent(testDataDir + "/property-assignment-on-non-present-module");
- QCOMPARE(runQbs(), 0);
- QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
-}
-
void TestBlackbox::propertyAssignmentInFailedModule()
{
QDir::setCurrent(testDataDir + "/property-assignment-in-failed-module");
@@ -3802,7 +3850,7 @@ void TestBlackbox::emptyProfile()
QDir::setCurrent(testDataDir + "/empty-profile");
const SettingsPtr s = settings();
- const Profile buildProfile(profileName(), s.get());
+ const qbs::Profile buildProfile(profileName(), s.get());
bool isMsvc = false;
auto toolchainType = buildProfile.value(QStringLiteral("qbs.toolchainType")).toString();
QbsRunParameters params;
@@ -3823,7 +3871,7 @@ void TestBlackbox::emptyProfile()
QDir::toNativeSeparators(
buildProfile.value(QStringLiteral("cpp.toolchainInstallPath")).toString());
auto paths = params.environment.value(QStringLiteral("PATH"))
- .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
+ .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
if (!tcPath.isEmpty() && !paths.contains(tcPath)) {
paths.prepend(tcPath);
params.environment.insert(
@@ -3923,15 +3971,16 @@ void TestBlackbox::errorInfo()
void TestBlackbox::escapedLinkerFlags()
{
- const SettingsPtr s = settings();
- const Profile buildProfile(profileName(), s.get());
- const QStringList toolchain = profileToolchain(buildProfile);
- if (!toolchain.contains("gcc"))
- QSKIP("escaped linker flags test only applies with gcc and GNU ld");
- if (targetOs() == HostOsInfo::HostOsMacos)
- QSKIP("Does not apply on macOS");
QDir::setCurrent(testDataDir + "/escaped-linker-flags");
- QbsRunParameters params(QStringList("products.app.escapeLinkerFlags:false"));
+ QbsRunParameters params("resolve", QStringList("products.app.escapeLinkerFlags:false"));
+ QCOMPARE(runQbs(params), 0);
+ const bool isGcc = m_qbsStdout.contains("is gcc: true");
+ const bool isNotGcc = m_qbsStdout.contains("is gcc: false");
+ if (isNotGcc)
+ QSKIP("escaped linker flags test only applies on plain unix with gcc and GNU ld");
+ QVERIFY(isGcc);
+
+ params.command = "build";
QCOMPARE(runQbs(params), 0);
params.command = "resolve";
params.arguments = QStringList() << "products.app.escapeLinkerFlags:true";
@@ -3988,26 +4037,24 @@ void TestBlackbox::exportedPropertyInDisabledProduct_data()
void TestBlackbox::systemRunPaths()
{
- const SettingsPtr s = settings();
- const Profile buildProfile(profileName(), s.get());
- switch (targetOs()) {
- case HostOsInfo::HostOsLinux:
- case HostOsInfo::HostOsMacos:
- case HostOsInfo::HostOsOtherUnix:
- break;
- default:
- QSKIP("only applies on Unix");
- }
-
const QString lddFilePath = findExecutable(QStringList() << "ldd");
if (lddFilePath.isEmpty())
QSKIP("ldd not found");
+
QDir::setCurrent(testDataDir + "/system-run-paths");
QFETCH(bool, setRunPaths);
rmDirR(relativeBuildDir());
- QbsRunParameters params;
+ QbsRunParameters params("resolve");
params.arguments << QString("project.setRunPaths:") + (setRunPaths ? "true" : "false");
QCOMPARE(runQbs(params), 0);
+ const bool isUnix = m_qbsStdout.contains("is unix: true");
+ const bool isNotUnix = m_qbsStdout.contains("is unix: false");
+ if (isNotUnix)
+ QSKIP("only applies on Unix");
+ QVERIFY(isUnix);
+
+ params.command = "build";
+ QCOMPARE(runQbs(params), 0);
QProcess ldd;
ldd.start(lddFilePath, QStringList() << relativeExecutableFilePath("app"));
QVERIFY2(ldd.waitForStarted(), qPrintable(ldd.errorString()));
@@ -4058,6 +4105,82 @@ void TestBlackbox::exportToOutsideSearchPath()
m_qbsStderr.constData());
}
+void TestBlackbox::exportsCMake()
+{
+ QFETCH(QStringList, arguments);
+
+ QDir::setCurrent(testDataDir + "/exports-cmake");
+ rmDirR(relativeBuildDir());
+ QbsRunParameters findCMakeParams("resolve", {"-f", "find-cmake.qbs"});
+ QCOMPARE(runQbs(findCMakeParams), 0);
+ const QString output = QString::fromLocal8Bit(m_qbsStdout);
+ const QRegularExpression pattern(
+ QRegularExpression::anchoredPattern(".*---(.*)---.*"),
+ QRegularExpression::DotMatchesEverythingOption);
+ const QRegularExpressionMatch match = pattern.match(output);
+ QVERIFY2(match.hasMatch(), qPrintable(output));
+ QCOMPARE(pattern.captureCount(), 1);
+ const QString jsonString = match.captured(1);
+ const QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8());
+ const QJsonObject jsonData = jsonDoc.object();
+
+ rmDirR(relativeBuildDir());
+ const QStringList exporterArgs{"-f", "exports-cmake.qbs"};
+ QbsRunParameters exporterRunParams("build", exporterArgs);
+ exporterRunParams.arguments << arguments;
+ QCOMPARE(runQbs(exporterRunParams), 0);
+
+ if (!jsonData.value(u"cmakeFound").toBool()) {
+ QSKIP("cmake is not installed");
+ return;
+ }
+
+ if (jsonData.value(u"crossCompiling").toBool()) {
+ QSKIP("test is not applicable with cross-compile toolchains");
+ return;
+ }
+
+ const auto cmakeFilePath = jsonData.value(u"cmakeFilePath").toString();
+ QVERIFY(!cmakeFilePath.isEmpty());
+
+ const auto generator = jsonData.value(u"generator").toString();
+ if (generator.isEmpty()) {
+ QSKIP("cannot detect cmake generator");
+ return;
+ }
+
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ const auto buildEnv = jsonData.value(u"buildEnv").toObject();
+ for (auto it = buildEnv.begin(), end = buildEnv.end(); it != end; ++it) {
+ env.insert(it.key(), it.value().toString());
+ }
+
+ const auto installPrefix = jsonData.value(u"installPrefix").toString();
+ const auto cmakePrefixPath = QFileInfo(relativeBuildDir()).absoluteFilePath() + "/install-root/"
+ + installPrefix + "/lib/cmake";
+ const auto sourceDirectory = testDataDir + "/exports-cmake/cmake";
+ QProcess configure;
+ configure.setProcessEnvironment(env);
+ configure.setWorkingDirectory(sourceDirectory);
+ configure.start(
+ cmakeFilePath, {".", "-DCMAKE_PREFIX_PATH=" + cmakePrefixPath, "-G" + generator});
+ QVERIFY(waitForProcessSuccess(configure, 120000));
+
+ QProcess build;
+ build.setProcessEnvironment(env);
+ build.setWorkingDirectory(sourceDirectory);
+ build.start(cmakeFilePath, QStringList{"--build", "."});
+ QVERIFY(waitForProcessSuccess(build));
+}
+
+void TestBlackbox::exportsCMake_data()
+{
+ QTest::addColumn<QStringList>("arguments");
+ QTest::newRow("dynamic lib") << QStringList("project.isStatic: false");
+ QTest::newRow("static lib") << QStringList("project.isStatic: true");
+ QTest::newRow("framework") << QStringList("project.isBundle: true");
+}
+
void TestBlackbox::exportsPkgconfig()
{
QDir::setCurrent(testDataDir + "/exports-pkgconfig");
@@ -4115,7 +4238,8 @@ void TestBlackbox::exportsQbs()
paramsExternalBuild.buildDirectory = QDir::currentPath() + "/external-consumer-profile";
paramsExternalBuild.expectFailure = true;
QVERIFY(runQbs(paramsExternalBuild) != 0);
- QVERIFY2(m_qbsStderr.contains("MyLib could not be loaded"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.contains("Dependency 'MyLib' not found for product 'consumer'"),
+ m_qbsStderr.constData());
// Removing the condition from the generated module leaves us with two conflicting
// candidates.
@@ -4564,7 +4688,7 @@ void TestBlackbox::cli()
QCOMPARE(status, 0);
const SettingsPtr s = settings();
- Profile p("qbs_autotests-cli", s.get());
+ qbs::Profile p("qbs_autotests-cli", s.get());
const QStringList toolchain = profileToolchain(p);
if (!p.exists() || !(toolchain.contains("dotnet") || toolchain.contains("mono")))
QSKIP("No suitable Common Language Infrastructure test profile");
@@ -4832,6 +4956,10 @@ void TestBlackbox::jsExtensionsBinaryFile()
QCOMPARE(data.at(5), char(0x05));
QCOMPARE(data.at(6), char(0x06));
QCOMPARE(data.at(7), char(0xFF));
+ QFile destination2("destination2.dat");
+ QVERIFY(destination2.exists());
+ QVERIFY(destination2.open(QIODevice::ReadOnly));
+ QCOMPARE(destination2.readAll(), data);
}
void TestBlackbox::lastModuleCandidateBroken()
@@ -4840,7 +4968,8 @@ void TestBlackbox::lastModuleCandidateBroken()
QbsRunParameters params;
params.expectFailure = true;
QVERIFY(runQbs(params) != 0);
- QVERIFY2(m_qbsStderr.contains("Module Foo could not be loaded"), m_qbsStderr);
+ QVERIFY2(m_qbsStderr.contains("Dependency 'Foo' not found for product "
+ "'last-module-candidate-broken'"), m_qbsStderr);
}
void TestBlackbox::ld()
@@ -4946,6 +5075,7 @@ void TestBlackbox::linkerVariant_data()
QTest::newRow("default") << QString();
QTest::newRow("bfd") << QString("bfd");
QTest::newRow("gold") << QString("gold");
+ QTest::newRow("mold") << QString("mold");
}
void TestBlackbox::linkerVariant()
@@ -5079,22 +5209,20 @@ void TestBlackbox::lexyaccOutputs_data()
void TestBlackbox::linkerLibraryDuplicates()
{
- const SettingsPtr s = settings();
- Profile buildProfile(profileName(), s.get());
- QStringList toolchain = profileToolchain(buildProfile);
- if (!toolchain.contains("gcc"))
- QSKIP("linkerLibraryDuplicates test only applies to GCC toolchain");
-
QDir::setCurrent(testDataDir + "/linker-library-duplicates");
rmDirR(relativeBuildDir());
-
QFETCH(QString, removeDuplicateLibraries);
QStringList runParams;
- if (!removeDuplicateLibraries.isEmpty()) {
+ if (!removeDuplicateLibraries.isEmpty())
runParams.append(removeDuplicateLibraries);
- }
QCOMPARE(runQbs(QbsRunParameters("resolve", runParams)), 0);
+ const bool isGcc = m_qbsStdout.contains("is gcc: true");
+ const bool isNotGcc = m_qbsStdout.contains("is gcc: false");
+ if (isNotGcc)
+ QSKIP("linkerLibraryDuplicates test only applies to GCC toolchain");
+ QVERIFY(isGcc);
+
QCOMPARE(runQbs(QStringList { "--command-echo-mode", "command-line" }), 0);
const QByteArrayList output = m_qbsStdout.split('\n');
QByteArray linkLine;
@@ -5159,20 +5287,20 @@ void TestBlackbox::linkerLibraryDuplicates_data()
void TestBlackbox::linkerScripts()
{
- const SettingsPtr s = settings();
- Profile buildProfile(profileName(), s.get());
- QStringList toolchain = profileToolchain(buildProfile);
- if (!toolchain.contains("gcc") || targetOs() != HostOsInfo::HostOsLinux)
- QSKIP("linker script test only applies to Linux ");
-
- QbsRunParameters runParams(QStringList()
-// << "--log-level" << "debug"
- << ("qbs.installRoot:" + QDir::currentPath()));
const QString sourceDir = QDir::cleanPath(testDataDir + "/linkerscripts");
+ QbsRunParameters runParams("resolve", {"qbs.installRoot:" + QDir::currentPath()});
runParams.buildDirectory = sourceDir + "/build";
runParams.workingDir = sourceDir;
QCOMPARE(runQbs(runParams), 0);
+ const bool isGcc = m_qbsStdout.contains("is Linux gcc: true");
+ const bool isNotGcc = m_qbsStdout.contains("is Linux gcc: false");
+ if (isNotGcc)
+ QSKIP("linker script test only applies to Linux");
+ QVERIFY(isGcc);
+
+ runParams.command = "build";
+ QCOMPARE(runQbs(runParams), 0);
const QString output = QString::fromLocal8Bit(m_qbsStderr);
const QRegularExpression pattern(QRegularExpression::anchoredPattern(".*---(.*)---.*"),
QRegularExpression::DotMatchesEverythingOption);
@@ -5930,22 +6058,19 @@ void TestBlackbox::protobuf_data()
QTest::addColumn<QStringList>("properties");
QTest::addColumn<bool>("hasModules");
QTest::addColumn<bool>("successExpected");
- QTest::newRow("cpp") << QString("addressbook_cpp.qbs") << QStringList() << false << true;
QTest::newRow("cpp-pkgconfig")
<< QString("addressbook_cpp.qbs")
- << QStringList("project.qbsModuleProviders:qbspkgconfig")
- << true
- << true;
+ << QStringList({"project.qbsModuleProviders:qbspkgconfig"}) << true << true;
QTest::newRow("objc") << QString("addressbook_objc.qbs") << QStringList() << false << true;
QTest::newRow("nanopb") << QString("addressbook_nanopb.qbs") << QStringList() << false << true;
- QTest::newRow("import") << QString("import.qbs") << QStringList() << false << true;
+ QTest::newRow("import") << QString("import.qbs") << QStringList() << true << true;
QTest::newRow("missing import dir") << QString("needs-import-dir.qbs")
- << QStringList() << false << false;
+ << QStringList() << true << false;
QTest::newRow("provided import dir")
<< QString("needs-import-dir.qbs")
- << QStringList("products.app.theImportDir:subdir") << false << true;
+ << QStringList("products.app.theImportDir:subdir") << true << true;
QTest::newRow("create proto library")
- << QString("create-proto-library.qbs") << QStringList() << false << true;
+ << QString("create-proto-library.qbs") << QStringList() << true << true;
}
void TestBlackbox::protobuf()
@@ -6105,6 +6230,10 @@ void TestBlackbox::qbsConfig()
if (!canWriteToSystemSettings) {
QVERIFY2(m_qbsStderr.contains("You do not have permission to write to that location."),
m_qbsStderr.constData());
+ } else {
+ // cleanup system settings
+ params.arguments = QStringList{"--system", "--unset", "key.subkey.scalar"};
+ QCOMPARE(runQbs(params) == 0, canWriteToSystemSettings);
}
}
@@ -6169,6 +6298,92 @@ void TestBlackbox::qbsConfigAddProfile_data()
<< QString("Profile properties must be key/value pairs");
}
+void TestBlackbox::qbsConfigImport()
+{
+ QFETCH(QString, format);
+
+ QDir::setCurrent(testDataDir + "/qbs-config-import-export");
+
+ QbsRunParameters params("config");
+ QTemporaryDir settingsDir;
+ QVERIFY(settingsDir.isValid());
+ const QStringList settingsDirArgs = QStringList{"--settings-dir", settingsDir.path()};
+ params.arguments = settingsDirArgs;
+ params.arguments << "--import" << "config." + format;
+
+ QCOMPARE(runQbs(params), 0);
+
+ params.arguments = settingsDirArgs;
+ params.arguments << "--list";
+ QCOMPARE(runQbs(params), 0);
+ const QByteArray output = m_qbsStdout;
+ const auto lines = output.split('\n');
+ QCOMPARE(lines.count(), 5);
+ QCOMPARE(lines[0], "group.key1: \"value1\"");
+ QCOMPARE(lines[1], "group.key2: \"value2\"");
+ QCOMPARE(lines[2], "key: \"value\"");
+ QCOMPARE(lines[3], "listKey: [\"valueOne\", \"valueTwo\"]");
+ QCOMPARE(lines[4], "");
+}
+
+void TestBlackbox::qbsConfigImport_data()
+{
+ QTest::addColumn<QString>("format");
+
+ QTest::newRow("text") << QStringLiteral("txt");
+ QTest::newRow("json") << QStringLiteral("json");
+}
+
+void TestBlackbox::qbsConfigExport()
+{
+ QFETCH(QString, format);
+
+ QDir::setCurrent(testDataDir + "/qbs-config-import-export");
+
+ QbsRunParameters params("config");
+ QTemporaryDir settingsDir;
+ const QString fileName = "config." + format;
+ const QString filePath = settingsDir.path() + "/" + fileName;
+ QVERIFY(settingsDir.isValid());
+ const QStringList commonArgs = QStringList{"--settings-dir", settingsDir.path(), "--user"};
+
+ std::pair<QString, QString> values[] = {
+ {"key", "value"},
+ {"listKey", "[\"valueOne\",\"valueTwo\"]"},
+ {"group.key1", "value1"},
+ {"group.key2", "value2"}
+ };
+
+ for (const auto &value: values) {
+ params.arguments = commonArgs;
+ params.arguments << value.first << value.second;
+ QCOMPARE(runQbs(params), 0);
+ }
+
+ params.arguments = commonArgs;
+ params.arguments << "--export" << filePath;
+
+ QCOMPARE(runQbs(params), 0);
+
+ QVERIFY(QFileInfo(filePath).canonicalPath() != QFileInfo(fileName).canonicalPath());
+
+ QFile exported(filePath);
+ QFile expected(fileName);
+
+ QVERIFY(exported.open(QIODevice::ReadOnly | QIODevice::Text));
+ QVERIFY(expected.open(QIODevice::ReadOnly | QIODevice::Text));
+
+ QCOMPARE(exported.readAll(), expected.readAll());
+}
+
+void TestBlackbox::qbsConfigExport_data()
+{
+ QTest::addColumn<QString>("format");
+
+ QTest::newRow("text") << QStringLiteral("txt");
+ QTest::newRow("json") << QStringLiteral("json");
+}
+
static QJsonObject getNextSessionPacket(QProcess &session, QByteArray &data)
{
int totalSize = -1;
@@ -6205,6 +6420,137 @@ static QJsonObject getNextSessionPacket(QProcess &session, QByteArray &data)
return QJsonDocument::fromJson(QByteArray::fromBase64(msg)).object();
}
+static void sendSessionPacket(QProcess &sessionProc, const QJsonObject &message)
+{
+ const QByteArray data = QJsonDocument(message).toJson().toBase64();
+ sessionProc.write("qbsmsg:");
+ sessionProc.write(QByteArray::number(data.length()));
+ sessionProc.write("\n");
+ sessionProc.write(data);
+}
+
+void TestBlackbox::qbsLanguageServer_data()
+{
+ QTest::addColumn<QString>("request");
+ QTest::addColumn<QString>("location");
+ QTest::addColumn<QString>("insertLocation");
+ QTest::addColumn<QString>("insertString");
+ QTest::addColumn<QString>("expectedReply");
+
+ QTest::addRow("follow to module") << "--goto-def"
+ << (testDataDir + "/lsp/lsp.qbs:4:9")
+ << QString() << QString()
+ << (testDataDir + "/lsp/modules/m/m.qbs:1:1");
+ QTest::addRow("follow to submodules")
+ << "--goto-def"
+ << (testDataDir + "/lsp/lsp.qbs:5:35")
+ << QString() << QString()
+ << ((testDataDir + "/lsp/modules/Prefix/m1/m1.qbs:1:1\n")
+ + (testDataDir + "/lsp/modules/Prefix/m2/m2.qbs:1:1\n")
+ + (testDataDir + "/lsp/modules/Prefix/m3/m3.qbs:1:1"));
+ QTest::addRow("follow to product")
+ << "--goto-def" << (testDataDir + "/lsp/lsp.qbs:9:19") << QString() << QString()
+ << (testDataDir + "/lsp/lsp.qbs:2:5");
+ QTest::addRow("follow to module, non-invalidating insert")
+ << "--goto-def" << (testDataDir + "/lsp/lsp.qbs:4:9") << "5:9"
+ << QString("property bool dummy\n") << (testDataDir + "/lsp/modules/m/m.qbs:1:1");
+ QTest::addRow("follow to module, invalidating insert")
+ << "--goto-def" << (testDataDir + "/lsp/lsp.qbs:4:9") << QString()
+ << QString("property bool dummy\n") << QString();
+ QTest::addRow("completion: LHS, module prefix")
+ << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString() << QString("P")
+ << QString("Prefix.m1\nPrefix.m2\nPrefix.m3");
+ QTest::addRow("completion: LHS, module name")
+ << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString() << QString("Prefix.m")
+ << QString("m1\nm2\nm3");
+ QTest::addRow("completion: LHS, module property right after dot")
+ << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString()
+ << QString("Prefix.m1.") << QString("p1 bool\np2 string\nx bool");
+ QTest::addRow("completion: LHS, module property with identifier prefix")
+ << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString()
+ << QString("Prefix.m1.p") << QString("p1 bool\np2 string");
+ QTest::addRow("completion: simple RHS, module property")
+ << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString()
+ << QString("property bool dummy: Prefix.m1.p") << QString("p1 bool\np2 string");
+ QTest::addRow("completion: complex RHS, module property")
+ << "--completion" << (testDataDir + "/lsp/lsp.qbs:7:1") << QString()
+ << QString("property bool dummy: { return Prefix.m1.p") << QString("p1 bool\np2 string");
+}
+
+void TestBlackbox::qbsLanguageServer()
+{
+ QFETCH(QString, request);
+ QFETCH(QString, location);
+ QFETCH(QString, insertLocation);
+ QFETCH(QString, insertString);
+ QFETCH(QString, expectedReply);
+
+ QDir::setCurrent(testDataDir + "/lsp");
+ QProcess sessionProc;
+ sessionProc.start(qbsExecutableFilePath, QStringList("session"));
+
+ QVERIFY(sessionProc.waitForStarted());
+
+ QByteArray incomingData;
+
+ // Wait for and verify hello packet.
+ QJsonObject receivedMessage = getNextSessionPacket(sessionProc, incomingData);
+ const QString socketPath = receivedMessage.value("lsp-socket").toString();
+ QVERIFY(!socketPath.isEmpty());
+
+ // Resolve project.
+ QJsonObject resolveMessage;
+ resolveMessage.insert("type", "resolve-project");
+ resolveMessage.insert("top-level-profile", profileName());
+ resolveMessage.insert("project-file-path", QDir::currentPath() + "/lsp.qbs");
+ resolveMessage.insert("build-root", QDir::currentPath());
+ resolveMessage.insert("settings-directory", settings()->baseDirectory());
+ sendSessionPacket(sessionProc, resolveMessage);
+ bool receivedReply = false;
+ while (!receivedReply) {
+ receivedMessage = getNextSessionPacket(sessionProc, incomingData);
+ QVERIFY(!receivedMessage.isEmpty());
+ const QString msgType = receivedMessage.value("type").toString();
+ if (msgType == "project-resolved") {
+ receivedReply = true;
+ const QJsonObject error = receivedMessage.value("error").toObject();
+ if (!error.isEmpty())
+ qDebug() << error;
+ QVERIFY(error.isEmpty());
+ }
+ }
+ QVERIFY(receivedReply);
+
+ // Employ client app to send request.
+ QProcess lspClient;
+ const QFileInfo qbsFileInfo(qbsExecutableFilePath);
+ const QString clientFilePath = HostOsInfo::appendExecutableSuffix(
+ qbsFileInfo.absolutePath() + "/qbs_lspclient");
+ QStringList args{"--socket", socketPath, request, location};
+ if (!insertString.isEmpty())
+ args << "--insert-code" << insertString;
+ if (!insertLocation.isEmpty())
+ args << "--insert-location" << insertLocation;
+ lspClient.start(clientFilePath, args);
+ QVERIFY2(lspClient.waitForStarted(), qPrintable(lspClient.errorString()));
+ QVERIFY2(lspClient.waitForFinished(), qPrintable(lspClient.errorString()));
+ QString errMsg;
+ if (lspClient.exitStatus() != QProcess::NormalExit)
+ errMsg = lspClient.errorString();
+ if (errMsg.isEmpty())
+ errMsg = QString::fromLocal8Bit(lspClient.readAllStandardError());
+ QVERIFY2(lspClient.exitCode() == 0, qPrintable(errMsg));
+ QVERIFY2(errMsg.isEmpty(), qPrintable(errMsg));
+ QString output = QString::fromUtf8(lspClient.readAllStandardOutput().trimmed());
+ output.replace("\r\n", "\n");
+ QCOMPARE(output, expectedReply);
+
+ QJsonObject quitRequest;
+ quitRequest.insert("type", "quit");
+ sendSessionPacket(sessionProc, quitRequest);
+ QVERIFY(sessionProc.waitForFinished(3000));
+}
+
void TestBlackbox::qbsSession()
{
QDir::setCurrent(testDataDir + "/qbs-session");
@@ -6221,11 +6567,7 @@ void TestBlackbox::qbsSession()
QVERIFY(sessionProc.waitForStarted());
const auto sendPacket = [&sessionProc](const QJsonObject &message) {
- const QByteArray data = QJsonDocument(message).toJson().toBase64();
- sessionProc.write("qbsmsg:");
- sessionProc.write(QByteArray::number(data.length()));
- sessionProc.write("\n");
- sessionProc.write(data);
+ sendSessionPacket(sessionProc, message);
};
static const auto envToJson = [](const QProcessEnvironment &env) {
@@ -6249,7 +6591,7 @@ void TestBlackbox::qbsSession()
// Wait for and verify hello packet.
QJsonObject receivedMessage = getNextSessionPacket(sessionProc, incomingData);
QCOMPARE(receivedMessage.value("type"), "hello");
- QCOMPARE(receivedMessage.value("api-level").toInt(), 3);
+ QCOMPARE(receivedMessage.value("api-level").toInt(), 5);
QCOMPARE(receivedMessage.value("api-compat-level").toInt(), 2);
// Resolve & verify structure
@@ -6265,6 +6607,7 @@ void TestBlackbox::qbsSession()
resolveMessage.insert("overridden-properties", overriddenValues);
resolveMessage.insert("environment", envToJson(QbsRunParameters::defaultEnvironment()));
resolveMessage.insert("data-mode", "only-if-changed");
+ resolveMessage.insert("max-job-count", 2);
resolveMessage.insert("log-time", true);
resolveMessage.insert("module-properties",
QJsonArray::fromStringList({"cpp.cxxLanguageVersion"}));
@@ -7338,7 +7681,7 @@ static bool haveMakeNsis()
<< QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS");
QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH")
- .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
+ .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
for (const QString &key : std::as_const(regKeys)) {
QSettings settings(key, QSettings::NativeFormat);
@@ -7576,7 +7919,7 @@ void TestBlackbox::generator_data()
void TestBlackbox::nodejs()
{
const SettingsPtr s = settings();
- Profile p(profileName(), s.get());
+ qbs::Profile p(profileName(), s.get());
int status;
findNodejs(&status);
@@ -7617,7 +7960,7 @@ void TestBlackbox::typescript()
QSKIP("Skip this test when running on GitHub");
const SettingsPtr s = settings();
- Profile p(profileName(), s.get());
+ qbs::Profile p(profileName(), s.get());
int status;
findTypeScript(&status);
@@ -7784,6 +8127,35 @@ void TestBlackbox::wildCardsAndRules()
QVERIFY(!m_qbsStdout.contains("creating output artifact"));
}
+void TestBlackbox::wildCardsAndChangeTracking_data()
+{
+ QTest::addColumn<QString>("dirToModify");
+ QTest::addColumn<bool>("expectReResolve");
+
+ QTest::newRow("root path") << QString(".") << false;
+ QTest::newRow("dir with recursive match") << QString("recursive1") << false;
+ QTest::newRow("non-recursive base dir") << QString("nonrecursive") << true;
+ QTest::newRow("empty base dir with file patterns") << QString("nonrecursive/empty") << true;
+}
+
+void TestBlackbox::wildCardsAndChangeTracking()
+{
+ QFETCH(QString, dirToModify);
+ QFETCH(bool, expectReResolve);
+
+ const QString srcDir = testDataDir + "/wildcards-and-change-tracking";
+ QDir::setCurrent(srcDir);
+ rmDirR("default");
+ QDir::current().mkdir("nonrecursive/empty");
+
+ QCOMPARE(runQbs({"resolve"}), 0);
+ QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
+ WAIT_FOR_NEW_TIMESTAMP();
+ touch(dirToModify + "/blubb.txt");
+ QCOMPARE(runQbs({"resolve"}), 0);
+ QCOMPARE(m_qbsStdout.contains("Resolving"), expectReResolve);
+}
+
void TestBlackbox::loadableModule()
{
QDir::setCurrent(testDataDir + QLatin1String("/loadablemodule"));
@@ -8104,6 +8476,12 @@ void TestBlackbox::movedFileDependency()
QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData());
}
+void TestBlackbox::msvcAsmLinkerFlags()
+{
+ QDir::setCurrent(testDataDir + "/msvc-asm-flags");
+ QCOMPARE(runQbs(), 0);
+}
+
void TestBlackbox::badInterpreter()
{
if (!HostOsInfo::isAnyUnixHost())
@@ -8225,6 +8603,7 @@ void TestBlackbox::groupsInModules()
QVERIFY(m_qbsStdout.contains("compiling helper4.c"));
QVERIFY(m_qbsStdout.contains("compiling helper5.c"));
QVERIFY(!m_qbsStdout.contains("compiling helper6.c"));
+ QVERIFY(m_qbsStdout.contains("compiling helper7.c"));
QCOMPARE(runQbs(params), 0);
QVERIFY(!m_qbsStdout.contains("compiling rock.coal to rock.diamond"));
@@ -8250,9 +8629,7 @@ void TestBlackbox::grpc_data()
QTest::addColumn<QStringList>("arguments");
QTest::addColumn<bool>("hasModules");
- QTest::newRow("cpp") << QString("grpc_cpp.qbs") << QStringList() << false;
-
- QStringList pkgConfigArgs("project.qbsModuleProviders:qbspkgconfig");
+ QStringList pkgConfigArgs({"project.qbsModuleProviders:qbspkgconfig"});
// on macOS, openSSL is hidden from pkg-config by default
if (qbs::Internal::HostOsInfo::isMacosHost()) {
pkgConfigArgs
diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h
index 4f8e898aa..5ba53ef52 100644
--- a/tests/auto/blackbox/tst_blackbox.h
+++ b/tests/auto/blackbox/tst_blackbox.h
@@ -126,6 +126,8 @@ private slots:
void exportedPropertyInDisabledProduct_data();
void exportRule();
void exportToOutsideSearchPath();
+ void exportsCMake();
+ void exportsCMake_data();
void exportsPkgconfig();
void exportsQbs();
void externalLibs();
@@ -207,6 +209,7 @@ private slots:
void missingOverridePrefix();
void moduleConditions();
void movedFileDependency();
+ void msvcAsmLinkerFlags();
void multipleChanges();
void multipleConfigurations();
void multiplexedTool();
@@ -229,6 +232,7 @@ private slots:
void overrideProjectProperties();
void pathProbe_data();
void pathProbe();
+ void pathListInProbe();
void pchChangeTracking();
void perGroupDefineInExportItem();
void pkgConfigProbe();
@@ -247,7 +251,6 @@ private slots:
void productDependenciesByType();
void productInExportedModule();
void productProperties();
- void propertyAssignmentOnNonPresentModule();
void propertyAssignmentInFailedModule();
void propertyChanges();
void propertyEvaluationContext();
@@ -261,6 +264,12 @@ private slots:
void qbsConfig();
void qbsConfigAddProfile();
void qbsConfigAddProfile_data();
+ void qbsConfigImport();
+ void qbsConfigImport_data();
+ void qbsConfigExport();
+ void qbsConfigExport_data();
+ void qbsLanguageServer_data();
+ void qbsLanguageServer();
void qbsSession();
void qbsVersion();
void qtBug51237();
@@ -277,11 +286,13 @@ private slots:
void rescueTransformerData();
void responseFiles();
void retaggedOutputArtifact();
+ void rpathlinkDeduplication();
void ruleConditions();
void ruleConnectionWithExcludedInputs();
void ruleCycle();
void ruleWithNoInputs();
void ruleWithNonRequiredInputs();
+ void runMultiplexed();
void sanitizer_data();
void sanitizer();
void scannerItem();
@@ -336,6 +347,8 @@ private slots:
void wholeArchive();
void wholeArchive_data();
void wildCardsAndRules();
+ void wildCardsAndChangeTracking_data();
+ void wildCardsAndChangeTracking();
void wildcardRenaming();
void zip();
void zip_data();
diff --git a/tests/auto/blackbox/tst_blackboxapple.cpp b/tests/auto/blackbox/tst_blackboxapple.cpp
index 01446c815..30e20a1c9 100644
--- a/tests/auto/blackbox/tst_blackboxapple.cpp
+++ b/tests/auto/blackbox/tst_blackboxapple.cpp
@@ -191,10 +191,12 @@ void TestBlackboxApple::initTestCase()
void TestBlackboxApple::appleMultiConfig()
{
const auto xcodeVersion = findXcodeVersion();
+ if (!xcodeVersion)
+ QSKIP("requires Xcode profile");
QDir::setCurrent(testDataDir + "/apple-multiconfig");
QCOMPARE(runQbs(QbsRunParameters(QStringList{
"qbs.installPrefix:''",
- QStringLiteral("project.xcodeVersion:") + xcodeVersion.toString()})), 0);
+ QStringLiteral("project.xcodeVersion:") + xcodeVersion->toString()})), 0);
if (m_qbsStdout.contains("isShallow: false")) {
QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp.app/Contents/MacOS/singleapp").isExecutable());
@@ -304,6 +306,10 @@ void TestBlackboxApple::appleMultiConfig()
void TestBlackboxApple::aggregateDependencyLinking()
{
const auto xcodeVersion = findXcodeVersion();
+
+ if (!xcodeVersion)
+ QSKIP("requires Xcode profile");
+
// XCode 11 produces warning about deprecation of 32-bit apps, but still works
const bool hasX86Mac = xcodeVersion < qbs::Version(12);
const bool hasArmMac = xcodeVersion >= qbs::Version(12, 2);
@@ -363,6 +369,9 @@ void TestBlackboxApple::assetCatalog()
rmDirR(relativeBuildDir());
+ if (!findXcode())
+ QSKIP("requires Xcode profile");
+
QbsRunParameters params;
const QString flattens = "modules.ib.flatten:" + QString(flatten ? "true" : "false");
const QString macosTarget = "modules.cpp.minimumMacosVersion:'10.15'";
@@ -703,28 +712,34 @@ void TestBlackboxApple::byteArrayInfoPlist()
void TestBlackboxApple::codesign()
{
+ QFETCH(int, expectedCount);
QFETCH(bool, isBundle);
QFETCH(bool, enableSigning);
QFETCH(bool, multiArch);
+ QFETCH(bool, multiVariant);
const auto xcodeVersion = findXcodeVersion();
+ if (!xcodeVersion)
+ QSKIP("requires Xcode profile");
+
QDir::setCurrent(testDataDir + "/codesign");
QbsRunParameters params(QStringList{"qbs.installPrefix:''"});
- params.arguments
- << QStringLiteral("project.isBundle:%1").arg(isBundle ? "true" : "false");
- params.arguments
- << QStringLiteral("project.enableSigning:%1").arg(enableSigning ? "true" : "false");
- if (multiArch)
- params.arguments << QStringLiteral("project.xcodeVersion:") + xcodeVersion.toString();
+ // the test can't use xcode module to determine version itself
+ params.arguments << QStringLiteral("project.xcodeVersion:") + xcodeVersion->toString();
+ params.arguments << QStringLiteral("project.isBundle:%1").arg(isBundle ? "true" : "false");
+ params.arguments << QStringLiteral("project.enableSigning:%1")
+ .arg(enableSigning ? "true" : "false");
+ params.arguments << QStringLiteral("project.multiArch:%1").arg(multiArch ? "true" : "false");
+ params.arguments << QStringLiteral("project.multiVariant:%1")
+ .arg(multiVariant ? "true" : "false");
rmDirR(relativeBuildDir());
QCOMPARE(runQbs(params), 0);
const int codeSignCount =
QString::fromUtf8(m_qbsStdout).count(QStringLiteral("codesign"));
- // We have 3 products, we have to sign each exactly once, even in the multiplexed case
- QCOMPARE(codeSignCount, enableSigning ? 3 : 0);
+ QCOMPARE(codeSignCount, expectedCount);
const auto appName = isBundle ? QStringLiteral("A.app") : QStringLiteral("A");
const auto appPath = defaultInstallRoot + "/" + appName;
@@ -769,16 +784,25 @@ void TestBlackboxApple::codesign()
void TestBlackboxApple::codesign_data()
{
+ QTest::addColumn<int>("expectedCount");
QTest::addColumn<bool>("isBundle");
QTest::addColumn<bool>("enableSigning");
QTest::addColumn<bool>("multiArch");
-
- QTest::newRow("bundle, unsigned") << true << false << false;
- QTest::newRow("standalone, unsigned") << false << false << false;
- QTest::newRow("bundle, signed") << true << true << false;
- QTest::newRow("standalone, signed") << false << true << false;
- QTest::newRow("bundle, signed, multiarch") << true << true << true;
- QTest::newRow("standalone, signed, multiarch") << false << true << true;
+ QTest::addColumn<bool>("multiVariant");
+
+ QTest::newRow("standalone, unsigned") << 0 << false << false << false << false;
+ QTest::newRow("bundle, unsigned") << 0 << true << false << false << false;
+ QTest::newRow("standalone, signed") << 3 << false << true << false << false;
+ QTest::newRow("bundle, signed") << 3 << true << true << false << false;
+ // here we only sign the resulting lipo artifact
+ QTest::newRow("standalone, signed, multiarch") << 3 << false << true << true << false;
+ QTest::newRow("bundle, signed, multiarch") << 3 << true << true << true << false;
+ // here we sign all artifacts
+ QTest::newRow("standalone, signed, multivariant") << 15 << false << true << false << true;
+ QTest::newRow("bundle, signed, multivariant") << 15 << true << true << false << true;
+ QTest::newRow("standalone, signed, multiarch, multivariant")
+ << 15 << false << true << true << true;
+ QTest::newRow("bundle, signed, multiarch, multivariant") << 15 << true << true << true << true;
}
void TestBlackboxApple::deploymentTarget()
@@ -791,6 +815,9 @@ void TestBlackboxApple::deploymentTarget()
QDir::setCurrent(testDataDir + "/deploymentTarget");
+ if (!findXcode())
+ QSKIP("requires Xcode profile");
+
QbsRunParameters params;
params.arguments = QStringList()
<< "--command-echo-mode"
@@ -1097,6 +1124,9 @@ void TestBlackboxApple::overrideInfoPlist()
void TestBlackboxApple::xcode()
{
+ if (!findXcode())
+ QSKIP("requires Xcode profile");
+
QProcess xcodeSelect;
xcodeSelect.start("xcode-select", QStringList() << "--print-path");
QVERIFY2(xcodeSelect.waitForStarted(), qPrintable(xcodeSelect.errorString()));
@@ -1113,7 +1143,7 @@ void TestBlackboxApple::xcode()
QVERIFY2(xcodebuildShowSdks.exitCode() == 0,
qPrintable(xcodebuildShowSdks.readAllStandardError().constData()));
const auto lines = QString::fromLocal8Bit(xcodebuildShowSdks.readAllStandardOutput().trimmed())
- .split('\n', QBS_SKIP_EMPTY_PARTS);
+ .split('\n', Qt::SkipEmptyParts);
for (const QString &line : lines) {
static const std::regex regexp("^.+\\s+\\-sdk\\s+([a-z]+)([0-9]+\\.[0-9]+)$");
const auto ln = line.toStdString();
@@ -1149,7 +1179,7 @@ void TestBlackboxApple::xcode()
QTEST_MAIN(TestBlackboxApple)
-QVariantMap TestBlackboxApple::findXcode(int *status)
+std::optional<QVariantMap> TestBlackboxApple::findXcode(int *status)
{
QTemporaryDir temp;
QbsRunParameters params = QStringList({"-f", testDataDir + "/find/find-xcode.qbs"});
@@ -1161,10 +1191,16 @@ QVariantMap TestBlackboxApple::findXcode(int *status)
+ "/xcode.json");
if (!file.open(QIODevice::ReadOnly))
return {};
- return QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
+ auto result = QJsonDocument::fromJson(file.readAll()).toVariant().toMap();
+ if (!result["present"].toBool())
+ return {};
+ return result;
}
-qbs::Version TestBlackboxApple::findXcodeVersion()
+std::optional<qbs::Version> TestBlackboxApple::findXcodeVersion()
{
- return qbs::Version::fromString(findXcode().value("version").toString());
+ const auto xcode = findXcode();
+ if (!xcode)
+ return {};
+ return qbs::Version::fromString(xcode->value("version").toString());
}
diff --git a/tests/auto/blackbox/tst_blackboxapple.h b/tests/auto/blackbox/tst_blackboxapple.h
index 9c329e961..a51414917 100644
--- a/tests/auto/blackbox/tst_blackboxapple.h
+++ b/tests/auto/blackbox/tst_blackboxapple.h
@@ -31,6 +31,8 @@
#include "tst_blackboxbase.h"
+#include <optional>
+
namespace qbs {
class Version;
} // namespace qbs
@@ -72,8 +74,8 @@ private slots:
void xcode();
private:
- QVariantMap findXcode(int *status = nullptr);
- qbs::Version findXcodeVersion();
+ std::optional<QVariantMap> findXcode(int *status = nullptr);
+ std::optional<qbs::Version> findXcodeVersion();
};
#endif // TST_BLACKBOXAPPLE_H
diff --git a/tests/auto/blackbox/tst_blackboxbase.cpp b/tests/auto/blackbox/tst_blackboxbase.cpp
index 8e85640db..17652e779 100644
--- a/tests/auto/blackbox/tst_blackboxbase.cpp
+++ b/tests/auto/blackbox/tst_blackboxbase.cpp
@@ -221,7 +221,7 @@ void TestBlackboxBase::validateTestProfile()
QString TestBlackboxBase::findExecutable(const QStringList &fileNames)
{
const QStringList path = QString::fromLocal8Bit(qgetenv("PATH"))
- .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
+ .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
for (const QString &fileName : fileNames) {
QFileInfo fi(fileName);
diff --git a/tests/auto/blackbox/tst_blackboxjava.cpp b/tests/auto/blackbox/tst_blackboxjava.cpp
index 05bd99078..4ddd6cdda 100644
--- a/tests/auto/blackbox/tst_blackboxjava.cpp
+++ b/tests/auto/blackbox/tst_blackboxjava.cpp
@@ -39,7 +39,9 @@
using qbs::Internal::HostOsInfo;
using qbs::Profile;
-TestBlackboxJava::TestBlackboxJava() : TestBlackboxBase (SRCDIR "/testdata-java", "blackbox-java")
+TestBlackboxJava::TestBlackboxJava()
+ : TestBlackboxBase (SRCDIR "/testdata-java", "blackbox-java"),
+ m_blacklistedJdks(qgetenv("QBS_AUTOTEST_JDK_BLACKLIST"))
{
}
@@ -148,8 +150,11 @@ void TestBlackboxJava::javaDependencyTracking()
QDir::setCurrent(testDataDir + "/java");
QbsRunParameters rp;
rp.arguments.push_back("--check-outputs");
- if (!jdkPath.isEmpty())
+ if (!jdkPath.isEmpty()) {
+ if (m_blacklistedJdks.contains(jdkPath))
+ QSKIP("skipping blacklisted JDK");
rp.arguments << ("modules.java.jdkPath:" + jdkPath);
+ }
if (!javaVersion.isEmpty())
rp.arguments << ("modules.java.languageVersion:'" + javaVersion + "'");
rmDirR(relativeBuildDir());
@@ -170,7 +175,7 @@ void TestBlackboxJava::javaDependencyTracking_data()
auto getSpecificJdkVersion = [](const QString &jdkVersion) -> QString {
if (HostOsInfo::isMacosHost()) {
QProcess java_home;
- java_home.start("/usr/libexec/java_home", QStringList() << "--version" << jdkVersion);
+ java_home.start("/usr/libexec/java_home", {"--version", jdkVersion, "--failfast"});
java_home.waitForFinished();
if (java_home.exitStatus() == QProcess::NormalExit && java_home.exitCode() == 0)
return QString::fromLocal8Bit(java_home.readAllStandardOutput().trimmed());
diff --git a/tests/auto/blackbox/tst_blackboxjava.h b/tests/auto/blackbox/tst_blackboxjava.h
index 68d8a7f80..e770306df 100644
--- a/tests/auto/blackbox/tst_blackboxjava.h
+++ b/tests/auto/blackbox/tst_blackboxjava.h
@@ -43,6 +43,9 @@ private slots:
void javaDependencyTracking();
void javaDependencyTracking_data();
void javaDependencyTrackingInnerClass();
+
+private:
+ const QStringList m_blacklistedJdks;
};
#endif // TST_BLACKBOX_H
diff --git a/tests/auto/blackbox/tst_blackboxproviders.cpp b/tests/auto/blackbox/tst_blackboxproviders.cpp
index ceb413510..3d64d32d5 100644
--- a/tests/auto/blackbox/tst_blackboxproviders.cpp
+++ b/tests/auto/blackbox/tst_blackboxproviders.cpp
@@ -49,58 +49,42 @@ TestBlackboxProviders::TestBlackboxProviders()
{
}
-void TestBlackboxProviders::fallbackModuleProvider_data()
+void TestBlackboxProviders::allowedValues()
{
- QTest::addColumn<bool>("fallbacksEnabledGlobally");
- QTest::addColumn<bool>("fallbacksEnabledInProduct");
- QTest::addColumn<QStringList>("pkgConfigLibDirs");
- QTest::addColumn<bool>("successExpected");
- QTest::newRow("without custom lib dir, fallbacks disabled globally and in product")
- << false << false << QStringList() << false;
- QTest::newRow("without custom lib dir, fallbacks disabled globally, enabled in product")
- << false << true << QStringList() << false;
- QTest::newRow("without custom lib dir, fallbacks enabled globally, disabled in product")
- << true << false << QStringList() << false;
- QTest::newRow("without custom lib dir, fallbacks enabled globally and in product")
- << true << true << QStringList() << false;
- QTest::newRow("with custom lib dir, fallbacks disabled globally and in product")
- << false << false << QStringList(testDataDir + "/fallback-module-provider/libdir")
- << false;
- QTest::newRow("with custom lib dir, fallbacks disabled globally, enabled in product")
- << false << true << QStringList(testDataDir + "/fallback-module-provider/libdir")
- << false;
- QTest::newRow("with custom lib dir, fallbacks enabled globally, disabled in product")
- << true << false << QStringList(testDataDir + "/fallback-module-provider/libdir")
- << false;
- QTest::newRow("with custom lib dir, fallbacks enabled globally and in product")
- << true << true << QStringList(testDataDir + "/fallback-module-provider/libdir")
- << true;
+ QFETCH(QStringList, arguments);
+ QFETCH(bool, expectFailure);
+
+ QDir::setCurrent(testDataDir + "/allowed-values");
+ rmDirR(relativeBuildDir());
+ QbsRunParameters params;
+ params.arguments = arguments;
+ params.expectFailure = expectFailure;
+
+ QVERIFY2(runQbs(params) == int(expectFailure), m_qbsStderr);
}
-void TestBlackboxProviders::fallbackModuleProvider()
+void TestBlackboxProviders::allowedValues_data()
{
- QFETCH(bool, fallbacksEnabledInProduct);
- QFETCH(bool, fallbacksEnabledGlobally);
- QFETCH(QStringList, pkgConfigLibDirs);
- QFETCH(bool, successExpected);
-
- QDir::setCurrent(testDataDir + "/fallback-module-provider");
- static const auto b2s = [](bool b) { return QString(b ? "true" : "false"); };
- QbsRunParameters resolveParams("resolve",
- QStringList{"modules.pkgconfig.libDirs:" + pkgConfigLibDirs.join(','),
- "products.p.fallbacksEnabled:" + b2s(fallbacksEnabledInProduct),
- "--force-probe-execution"});
- if (!fallbacksEnabledGlobally)
- resolveParams.arguments << "--no-fallback-module-provider";
- QCOMPARE(runQbs(resolveParams), 0);
- const bool pkgConfigPresent = m_qbsStdout.contains("pkg-config present: true");
- const bool pkgConfigNotPresent = m_qbsStdout.contains("pkg-config present: false");
- QVERIFY(pkgConfigPresent != pkgConfigNotPresent);
- if (pkgConfigNotPresent)
- successExpected = false;
- QbsRunParameters buildParams;
- buildParams.expectFailure = !successExpected;
- QCOMPARE(runQbs(buildParams) == 0, successExpected);
+ QTest::addColumn<QStringList>("arguments");
+ QTest::addColumn<bool>("expectFailure");
+
+ QTest::newRow("invalid js value") << QStringList{} << true;
+ QTest::newRow("invalid variant value")
+ << QStringList{"moduleProviders.provider.aProperty:three"} << true;
+ QTest::newRow("valid variant value")
+ << QStringList{"moduleProviders.provider.aProperty:one"} << false;
+}
+
+void TestBlackboxProviders::brokenProvider()
+{
+ QDir::setCurrent(testDataDir + "/broken-provider");
+ QbsRunParameters params;
+ params.expectFailure = true;
+ QVERIFY(runQbs(params) != 0);
+
+ QVERIFY(m_qbsStderr.contains("Error executing provider for module 'qbsothermodule'"));
+ QVERIFY(m_qbsStderr.contains("Error executing provider for module 'qbsmetatestmodule'"));
+ QCOMPARE(m_qbsStderr.count("This provider is broken"), 2);
}
void TestBlackboxProviders::moduleProviders()
@@ -179,6 +163,22 @@ void TestBlackboxProviders::moduleProvidersCache()
QCOMPARE(m_qbsStderr.count("Re-using provider \"provider_a\" from cache"), 2);
}
+void TestBlackboxProviders::nonEagerModuleProvider()
+{
+ QDir::setCurrent(testDataDir + "/non-eager-provider");
+
+ QbsRunParameters params("resolve");
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains(("Running setup script for qbsmetatestmodule")), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("Running setup script for qbsothermodule")), m_qbsStdout);
+ QVERIFY2(!m_qbsStdout.contains(("Running setup script for nonexistentmodule")), m_qbsStdout);
+
+ QVERIFY2(m_qbsStdout.contains(("p1.qbsmetatestmodule.prop: from_provider_a")),
+ m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("p1.qbsothermodule.prop: from_provider_a")),
+ m_qbsStdout);
+}
+
void TestBlackboxProviders::probeInModuleProvider()
{
QDir::setCurrent(testDataDir + "/probe-in-module-provider");
@@ -193,6 +193,7 @@ void TestBlackboxProviders::probeInModuleProvider()
touch("probe-in-module-provider.qbs");
QCOMPARE(runQbs(), 0);
QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.boolProp: true"), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.prop: \"value\""), m_qbsStdout);
QVERIFY2(!m_qbsStdout.contains("Running probe"), m_qbsStdout);
}
@@ -365,4 +366,12 @@ void TestBlackboxProviders::qbspkgconfigModuleProvider()
QCOMPARE(runQbs(params), 0);
}
+void TestBlackboxProviders::removalVersion()
+{
+ QDir::setCurrent(testDataDir + "/removal-version");
+ QCOMPARE(runQbs(), 0);
+ QVERIFY(m_qbsStderr.contains(
+ "Property 'deprecated' was scheduled for removal in version 2.2.0, but is still present"));
+}
+
QTEST_MAIN(TestBlackboxProviders)
diff --git a/tests/auto/blackbox/tst_blackboxproviders.h b/tests/auto/blackbox/tst_blackboxproviders.h
index 017cc1c5e..dd0039013 100644
--- a/tests/auto/blackbox/tst_blackboxproviders.h
+++ b/tests/auto/blackbox/tst_blackboxproviders.h
@@ -41,10 +41,12 @@ public:
TestBlackboxProviders();
private slots:
- void fallbackModuleProvider_data();
- void fallbackModuleProvider();
+ void allowedValues();
+ void allowedValues_data();
+ void brokenProvider();
void moduleProviders();
void moduleProvidersCache();
+ void nonEagerModuleProvider();
void probeInModuleProvider();
void providersProperties();
void qbsModulePropertiesInProviders();
@@ -55,6 +57,7 @@ private slots:
void qbsModuleProvidersCompatibility();
void qbsModuleProvidersCompatibility_data();
void qbspkgconfigModuleProvider();
+ void removalVersion();
};
#endif // TST_BLACKBOXPROVIDERS_H
diff --git a/tests/auto/blackbox/tst_blackboxqt.cpp b/tests/auto/blackbox/tst_blackboxqt.cpp
index 6bb7838f6..b083a97e8 100644
--- a/tests/auto/blackbox/tst_blackboxqt.cpp
+++ b/tests/auto/blackbox/tst_blackboxqt.cpp
@@ -38,7 +38,6 @@
#define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir)
using qbs::Internal::HostOsInfo;
-using qbs::Profile;
TestBlackboxQt::TestBlackboxQt() : TestBlackboxBase (SRCDIR "/testdata-qt", "blackbox-qt")
{
@@ -240,7 +239,7 @@ void TestBlackboxQt::mixedBuildVariants()
{
QDir::setCurrent(testDataDir + "/mixed-build-variants");
const SettingsPtr s = settings();
- Profile profile(profileName(), s.get());
+ qbs::Profile profile(profileName(), s.get());
if (profileToolchain(profile).contains("msvc")) {
QbsRunParameters params;
params.arguments << "qbs.buildVariant:debug";
@@ -397,6 +396,20 @@ void TestBlackboxQt::pkgconfigQt_data()
<< QStringList({"moduleProviders.qbspkgconfig.sysroot:/some/fake/sysroot"}) << false;
}
+void TestBlackboxQt::pkgconfigNoQt()
+{
+ QDir::setCurrent(testDataDir + "/pkgconfig-qt");
+ rmDirR(relativeBuildDir());
+ QbsRunParameters params("build", {"-f", "pkgconfig-qt.qbs"});
+ params.arguments << "moduleProviders.qbspkgconfig.libDirs:nonexistent";
+ params.expectFailure = true;
+
+ QCOMPARE(runQbs(params) == 0, false);
+ QVERIFY2(m_qbsStderr.contains("Dependency 'Qt.core' not found for product 'p'"), m_qbsStderr);
+ // QBS-1777: basic check for JS exceptions in case of missing Qt
+ QVERIFY2(!m_qbsStderr.contains("Error executing provider for module 'Qt.core'"), m_qbsStderr);
+}
+
void TestBlackboxQt::pluginMetaData()
{
QDir::setCurrent(testDataDir + "/plugin-meta-data");
@@ -470,22 +483,36 @@ void TestBlackboxQt::pluginSupport()
}
}
+void TestBlackboxQt::qdoc()
+{
+ QDir::setCurrent(testDataDir + "/qdoc");
+ QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
+ if (m_qbsStdout.contains("Qt is too old"))
+ QSKIP("Skip test since qdoc3 does not work properly");
+ QCOMPARE(runQbs(), 0);
+ QVERIFY(QFileInfo(relativeProductBuildDir("QDoc Test") + "/qdoctest.qch").exists());
+}
+
void TestBlackboxQt::qmlDebugging()
{
QDir::setCurrent(testDataDir + "/qml-debugging");
QCOMPARE(runQbs(), 0);
- const SettingsPtr s = settings();
- Profile profile(profileName(), s.get());
- if (!profileToolchain(profile).contains("gcc"))
- return;
+
+ const bool isGcc = m_qbsStdout.contains("is gcc: true");
+ const bool isNotGcc = m_qbsStdout.contains("is gcc: false");
+ if (isNotGcc)
+ QSKIP("The remainder of this test only applies to gcc");
+ QVERIFY(isGcc);
+
QProcess nm;
nm.start("nm", QStringList(relativeExecutableFilePath("debuggable-app")));
- if (nm.waitForStarted()) { // Let's ignore hosts without nm.
- QVERIFY2(nm.waitForFinished(), qPrintable(nm.errorString()));
- QVERIFY2(nm.exitCode() == 0, nm.readAllStandardError().constData());
- const QByteArray output = nm.readAllStandardOutput();
- QVERIFY2(output.toLower().contains("debugginghelper"), output.constData());
- }
+ if (!nm.waitForStarted())
+ QSKIP("The remainder of this test requires nm");
+
+ QVERIFY2(nm.waitForFinished(), qPrintable(nm.errorString()));
+ QVERIFY2(nm.exitCode() == 0, nm.readAllStandardError().constData());
+ const QByteArray output = nm.readAllStandardOutput();
+ QVERIFY2(output.toLower().contains("debugginghelper"), output.constData());
}
void TestBlackboxQt::qobjectInObjectiveCpp()
diff --git a/tests/auto/blackbox/tst_blackboxqt.h b/tests/auto/blackbox/tst_blackboxqt.h
index 0ea8dd60b..da395b7d4 100644
--- a/tests/auto/blackbox/tst_blackboxqt.h
+++ b/tests/auto/blackbox/tst_blackboxqt.h
@@ -62,9 +62,11 @@ private slots:
void pkgconfig();
void pkgconfigQt();
void pkgconfigQt_data();
+ void pkgconfigNoQt();
void pluginMetaData();
void pluginSupport_data();
void pluginSupport();
+ void qdoc();
void qmlDebugging();
void qobjectInObjectiveCpp();
void qmlTypeRegistrar_data();
diff --git a/tests/auto/blackbox/tst_blackboxtutorial.cpp b/tests/auto/blackbox/tst_blackboxtutorial.cpp
new file mode 100644
index 000000000..49de448a1
--- /dev/null
+++ b/tests/auto/blackbox/tst_blackboxtutorial.cpp
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: https://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 "tst_blackboxtutorial.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qdiriterator.h>
+
+static QStringList collectProjects(const QString &dirPath)
+{
+ QStringList result;
+ QDir dir(dirPath);
+ const auto subDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
+ for (const auto &subDir : subDirs) {
+ const auto path = dir.filePath(subDir);
+ if (!QFileInfo::exists(path + "/myproject.qbs"))
+ continue;
+ result.append(dir.relativeFilePath(path));
+ }
+ return result;
+}
+
+TestBlackboxTutorial::TestBlackboxTutorial()
+ : TestBlackboxBase(SRCDIR "/../../../tutorial/", "blackbox-tutorial")
+{}
+
+void TestBlackboxTutorial::tutorial_data()
+{
+ QTest::addColumn<QString>("project");
+
+ const auto projects = collectProjects(testDataDir);
+ for (const auto &project : projects) {
+ QTest::newRow(project.toUtf8().data()) << project;
+ }
+}
+
+void TestBlackboxTutorial::tutorial()
+{
+ QFETCH(QString, project);
+
+ QVERIFY(QDir::setCurrent(testDataDir + "/" + project));
+ QCOMPARE(runQbs(), 0);
+}
+
+QTEST_MAIN(TestBlackboxTutorial)
diff --git a/tests/auto/blackbox/tst_blackboxtutorial.h b/tests/auto/blackbox/tst_blackboxtutorial.h
new file mode 100644
index 000000000..2e84d6a96
--- /dev/null
+++ b/tests/auto/blackbox/tst_blackboxtutorial.h
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: https://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 TST_BLACKBOXTUTORIAL_H
+#define TST_BLACKBOXTUTORIAL_H
+
+#include "tst_blackboxbase.h"
+
+class TestBlackboxTutorial : public TestBlackboxBase
+{
+ Q_OBJECT
+
+public:
+ TestBlackboxTutorial();
+
+private slots:
+ void tutorial_data();
+ void tutorial();
+};
+
+#endif // TST_BLACKBOXTUTORIAL_H
diff --git a/tests/auto/blackbox/tst_blackboxwindows.cpp b/tests/auto/blackbox/tst_blackboxwindows.cpp
index 1c487bc52..57bd7f947 100644
--- a/tests/auto/blackbox/tst_blackboxwindows.cpp
+++ b/tests/auto/blackbox/tst_blackboxwindows.cpp
@@ -228,7 +228,7 @@ static bool haveWiX(const Profile &profile)
<< QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Installer XML\\");
QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH")
- .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
+ .split(HostOsInfo::pathListSeparator(), Qt::SkipEmptyParts);
for (const QString &key : std::as_const(regKeys)) {
const QStringList versions = QSettings(key, QSettings::NativeFormat).childGroups();
diff --git a/tests/auto/cmdlineparser/tst_cmdlineparser.cpp b/tests/auto/cmdlineparser/tst_cmdlineparser.cpp
index f617d41c7..6a8b44f89 100644
--- a/tests/auto/cmdlineparser/tst_cmdlineparser.cpp
+++ b/tests/auto/cmdlineparser/tst_cmdlineparser.cpp
@@ -26,6 +26,8 @@
**
****************************************************************************/
+#include "../shared.h"
+
#include <app/qbs/parser/commandlineparser.h>
#include <app/shared/logging/consolelogger.h>
#include <tools/buildoptions.h>
@@ -55,6 +57,25 @@ private slots:
m_fileArgs = QStringList() << "-f" << m_projectFile.fileName();
}
+ void testResolve_data()
+ {
+ QTest::addColumn<QStringList>("args");
+ QTest::addColumn<int>("expectedJobCount");
+
+ QTest::newRow("default job count") << QStringList() << BuildOptions::defaultMaxJobCount();
+ QTest::newRow("explicit job count") << QStringList("-j5") << 5;
+ }
+ void testResolve()
+ {
+ QFETCH(QStringList, args);
+ QFETCH(int, expectedJobCount);
+
+ CommandLineParser parser;
+ QVERIFY(parser.parseCommandLine(QStringList("resolve") << args << m_fileArgs));
+ QCOMPARE(parser.command(), ResolveCommandType);
+ QCOMPARE(parser.jobCount(profileName()), expectedJobCount);
+ }
+
void testValidCommandLine()
{
QStringList args;
diff --git a/tests/auto/language/testdata/erroneous/dependency_cycle3a.qbs b/tests/auto/language/testdata/erroneous/dependency_cycle3a.qbs
new file mode 100644
index 000000000..a0660c074
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/dependency_cycle3a.qbs
@@ -0,0 +1,11 @@
+Project {
+ Product {
+ name: "B"
+ Depends { productTypes: ["a"] }
+ }
+ Product {
+ type: ["a"]
+ name: "A"
+ Depends { name: "B" }
+ }
+}
diff --git a/tests/auto/language/testdata/erroneous/frozen-object-list.qbs b/tests/auto/language/testdata/erroneous/frozen-object-list.qbs
new file mode 100644
index 000000000..8bbd2b413
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/frozen-object-list.qbs
@@ -0,0 +1,17 @@
+
+Product {
+ Probe {
+ id: probe
+ property varList output
+ configure: {
+ output = [{"key": "value"}];
+ found = true;
+ }
+ }
+
+ property var test: {
+ var result = probe.output;
+ result.push({});
+ return result;
+ }
+}
diff --git a/tests/auto/language/testdata/erroneous/frozen-object.qbs b/tests/auto/language/testdata/erroneous/frozen-object.qbs
new file mode 100644
index 000000000..0f891d04b
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/frozen-object.qbs
@@ -0,0 +1,18 @@
+
+Product {
+ Probe {
+ id: probe
+ property var output
+ configure: {
+ output = {"key": "value"}
+ found = true
+ }
+ }
+
+ property var test: {
+ "use strict"
+ var result = probe.output;
+ result.key = "newValue";
+ return result;
+ }
+}
diff --git a/tests/auto/language/testdata/erroneous/module-property-binding-in-project.qbs b/tests/auto/language/testdata/erroneous/module-property-binding-in-project.qbs
new file mode 100644
index 000000000..667088c17
--- /dev/null
+++ b/tests/auto/language/testdata/erroneous/module-property-binding-in-project.qbs
@@ -0,0 +1,3 @@
+Project {
+ qbs.sysroot: "/"
+}
diff --git a/tests/auto/language/testdata/invalid-prop-on-non-required-module/invalid-prop-on-non-required-module.qbs b/tests/auto/language/testdata/invalid-prop-on-non-required-module/invalid-prop-on-non-required-module.qbs
new file mode 100644
index 000000000..80ae6ad93
--- /dev/null
+++ b/tests/auto/language/testdata/invalid-prop-on-non-required-module/invalid-prop-on-non-required-module.qbs
@@ -0,0 +1,19 @@
+Project {
+ property bool useExistingModule
+
+ Product {
+ name: "a"
+ condition: project.useExistingModule
+ Depends { name: "deploader" }
+ Depends { name: "dep" }
+ dep.nosuchprop: true
+ }
+
+ Product {
+ name: "b"
+ condition: !project.useExistingModule
+ Depends { name: "deploader" }
+ Depends { name: "random"; required: false }
+ random.nosuchprop: true
+ }
+}
diff --git a/tests/auto/language/testdata/invalid-prop-on-non-required-module/modules/dep/dep.qbs b/tests/auto/language/testdata/invalid-prop-on-non-required-module/modules/dep/dep.qbs
new file mode 100644
index 000000000..5e45122de
--- /dev/null
+++ b/tests/auto/language/testdata/invalid-prop-on-non-required-module/modules/dep/dep.qbs
@@ -0,0 +1 @@
+Module {}
diff --git a/tests/auto/language/testdata/invalid-prop-on-non-required-module/modules/deploader/deploader.qbs b/tests/auto/language/testdata/invalid-prop-on-non-required-module/modules/deploader/deploader.qbs
new file mode 100644
index 000000000..15a1b5309
--- /dev/null
+++ b/tests/auto/language/testdata/invalid-prop-on-non-required-module/modules/deploader/deploader.qbs
@@ -0,0 +1,7 @@
+Module {
+ // This indirection exists to properly model QBS-1776.
+ // "deploader" corresponds to "bundle", and "dep" corresponds to "codesign"
+ Depends { condition: project.useExistingModule; name: "dep"; required: false }
+
+ Depends { condition: !project.useExistingModule; name: "random"; required: false }
+}
diff --git a/tests/auto/language/testdata/local-profile-as-top-level-profile.qbs b/tests/auto/language/testdata/local-profile-as-top-level-profile.qbs
new file mode 100644
index 000000000..9bca3c599
--- /dev/null
+++ b/tests/auto/language/testdata/local-profile-as-top-level-profile.qbs
@@ -0,0 +1,7 @@
+Product {
+ Profile {
+ name: "test-profile"
+ qbs.architecture: "arm"
+ qbs.targetPlatform: "macos"
+ }
+}
diff --git a/tests/auto/language/testdata/module-name-collisions/complex-collision.qbs b/tests/auto/language/testdata/module-name-collisions/complex-collision.qbs
new file mode 100644
index 000000000..cd55aa946
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/complex-collision.qbs
@@ -0,0 +1,4 @@
+Product {
+ Depends { name: "prefix1.middle1" }
+ Depends { name: "prefix1.middle1.suffix1" }
+}
diff --git a/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/middle1.qbs b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/middle1.qbs
new file mode 100644
index 000000000..5e45122de
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/middle1.qbs
@@ -0,0 +1 @@
+Module {}
diff --git a/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/suffix1/suffix1.qbs b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/suffix1/suffix1.qbs
new file mode 100644
index 000000000..5e45122de
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/suffix1/suffix1.qbs
@@ -0,0 +1 @@
+Module {}
diff --git a/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/suffix2/suffix2.qbs b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/suffix2/suffix2.qbs
new file mode 100644
index 000000000..5e45122de
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle1/suffix2/suffix2.qbs
@@ -0,0 +1 @@
+Module {}
diff --git a/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle2/suffix/suffix.qbs b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle2/suffix/suffix.qbs
new file mode 100644
index 000000000..5e45122de
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/middle2/suffix/suffix.qbs
@@ -0,0 +1 @@
+Module {}
diff --git a/tests/auto/language/testdata/module-name-collisions/modules/prefix1/prefix1.qbs b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/prefix1.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/prefix1.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/language/testdata/erroneous/modules/prefix1/suffix/suffix.qbs b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/suffix/suffix.qbs
index 218a4feb7..218a4feb7 100644
--- a/tests/auto/language/testdata/erroneous/modules/prefix1/suffix/suffix.qbs
+++ b/tests/auto/language/testdata/module-name-collisions/modules/prefix1/suffix/suffix.qbs
diff --git a/tests/auto/language/testdata/module-name-collisions/modules/prefix2/prefix2.qbs b/tests/auto/language/testdata/module-name-collisions/modules/prefix2/prefix2.qbs
new file mode 100644
index 000000000..a5aaa6f8b
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/modules/prefix2/prefix2.qbs
@@ -0,0 +1,3 @@
+Module {
+ Depends { name: "prefix2.suffix" }
+}
diff --git a/tests/auto/language/testdata/module-name-collisions/modules/prefix2/suffix/suffix.qbs b/tests/auto/language/testdata/module-name-collisions/modules/prefix2/suffix/suffix.qbs
new file mode 100644
index 000000000..84957060c
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/modules/prefix2/suffix/suffix.qbs
@@ -0,0 +1,2 @@
+Module {
+}
diff --git a/tests/auto/language/testdata/module-name-collisions/no-collision1.qbs b/tests/auto/language/testdata/module-name-collisions/no-collision1.qbs
new file mode 100644
index 000000000..e5a94ad66
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/no-collision1.qbs
@@ -0,0 +1,4 @@
+Product {
+ Depends { name: "prefix1.middle1.suffix1" }
+ Depends { name: "prefix1.middle1.suffix2" }
+}
diff --git a/tests/auto/language/testdata/module-name-collisions/no-collision2.qbs b/tests/auto/language/testdata/module-name-collisions/no-collision2.qbs
new file mode 100644
index 000000000..664ec1729
--- /dev/null
+++ b/tests/auto/language/testdata/module-name-collisions/no-collision2.qbs
@@ -0,0 +1,4 @@
+Product {
+ Depends { name: "prefix1.middle1" }
+ Depends { name: "prefix1.middle2.suffix" }
+}
diff --git a/tests/auto/language/testdata/erroneous/same-module-prefix1.qbs b/tests/auto/language/testdata/module-name-collisions/simple-collision1.qbs
index 8aba31c2a..8aba31c2a 100644
--- a/tests/auto/language/testdata/erroneous/same-module-prefix1.qbs
+++ b/tests/auto/language/testdata/module-name-collisions/simple-collision1.qbs
diff --git a/tests/auto/language/testdata/erroneous/same-module-prefix2.qbs b/tests/auto/language/testdata/module-name-collisions/simple-collision2.qbs
index 6679091c2..6679091c2 100644
--- a/tests/auto/language/testdata/erroneous/same-module-prefix2.qbs
+++ b/tests/auto/language/testdata/module-name-collisions/simple-collision2.qbs
diff --git a/tests/auto/language/testdata/module-parameters/module-parameters.qbs b/tests/auto/language/testdata/module-parameters/module-parameters.qbs
new file mode 100644
index 000000000..48169d601
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/module-parameters.qbs
@@ -0,0 +1,35 @@
+Project {
+ property bool overrideFromModule
+ property bool overrideFromExport
+ property bool overrideFromProduct
+
+ Product {
+ name: "dep"
+ Export {
+ Depends {
+ name: "higher";
+ condition: project.overrideFromExport
+ lower.param: "fromExportDepends"
+ }
+ Parameters { lower.param: "fromParameters" }
+ }
+ }
+ Product {
+ name: "main"
+
+ Depends {
+ name: "dep"
+ condition: project.overrideFromProduct
+ lower.param: "fromProductDepends"
+ }
+ Depends {
+ name: "higher"
+ condition: project.overrideFromProduct
+ lower.param: "fromProductDepends"
+ }
+ Depends { name: "dep"; condition: !project.overrideFromProduct }
+ Depends { name: "higher"; condition: !project.overrideFromProduct }
+ Depends { name: "highest" }
+ Depends { name: "broken"; required: false }
+ }
+}
diff --git a/tests/auto/language/testdata/module-parameters/modules/broken/broken.qbs b/tests/auto/language/testdata/module-parameters/modules/broken/broken.qbs
new file mode 100644
index 000000000..ae7b4c4ef
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/modules/broken/broken.qbs
@@ -0,0 +1,4 @@
+Module {
+ Depends { name: "higher"; lower.param: "shouldNeverAppear" }
+ validate: { throw "As the name indicates, this module is broken."; }
+}
diff --git a/tests/auto/language/testdata/module-parameters/modules/higher/higher.qbs b/tests/auto/language/testdata/module-parameters/modules/higher/higher.qbs
new file mode 100644
index 000000000..006e05a93
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/modules/higher/higher.qbs
@@ -0,0 +1,4 @@
+Module {
+ Depends { name: "lower" }
+ Parameters { lower.param: "fromParameters" }
+}
diff --git a/tests/auto/language/testdata/module-parameters/modules/highest/highest.qbs b/tests/auto/language/testdata/module-parameters/modules/highest/highest.qbs
new file mode 100644
index 000000000..83b0a0a03
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/modules/highest/highest.qbs
@@ -0,0 +1,7 @@
+Module {
+ Depends {
+ name: "higher"
+ condition: project.overrideFromModule
+ lower.param: "fromModuleDepends"
+ }
+}
diff --git a/tests/auto/language/testdata/module-parameters/modules/lower/lower.qbs b/tests/auto/language/testdata/module-parameters/modules/lower/lower.qbs
new file mode 100644
index 000000000..11436ecd8
--- /dev/null
+++ b/tests/auto/language/testdata/module-parameters/modules/lower/lower.qbs
@@ -0,0 +1,3 @@
+Module {
+ Parameter { property string param: "origin" }
+}
diff --git a/tests/auto/language/testdata/probes-and-multiplexing.qbs b/tests/auto/language/testdata/probes-and-multiplexing.qbs
new file mode 100644
index 000000000..38de08aee
--- /dev/null
+++ b/tests/auto/language/testdata/probes-and-multiplexing.qbs
@@ -0,0 +1,15 @@
+Product {
+ multiplexByQbsProperties: "architectures"
+ qbs.architectures: ["x86", "x86_64", "arm"]
+ property string archFromProbe: theProbe.archOut
+ Probe {
+ id: theProbe
+ property string archIn: qbs.architecture
+ property string archOut
+ configure: { archOut = archIn; }
+ }
+ Group {
+ name: "theGroup"
+ qbs.sysroot: "/" + theProbe.archOut
+ }
+}
diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp
index b6dd78141..456e8b9d0 100644
--- a/tests/auto/language/tst_language.cpp
+++ b/tests/auto/language/tst_language.cpp
@@ -86,6 +86,27 @@ static QString testProject(const char *fileName) {
return testDataDir() + QLatin1Char('/') + QLatin1String(fileName);
}
+class JSSourceValueCreator
+{
+ FileContextPtr m_fileContext;
+ std::vector<std::unique_ptr<QString>> m_strings;
+public:
+ JSSourceValueCreator(const FileContextPtr &fileContext)
+ : m_fileContext(fileContext)
+ {
+ }
+
+ JSSourceValuePtr create(const QString &sourceCode)
+ {
+ JSSourceValuePtr value = JSSourceValue::create();
+ value->setFile(m_fileContext);
+ auto str = std::make_unique<QString>(sourceCode);
+ value->setSourceCode(*str.get());
+ m_strings.push_back(std::move(str));
+ return value;
+ }
+};
+
TestLanguage::TestLanguage(ILogSink *logSink, Settings *settings)
: m_logSink(logSink)
, m_settings(settings)
@@ -159,18 +180,22 @@ TopLevelProjectPtr TestLanguage::resolveProject(const char *relProjectFilePath)
if (relProjectFilePath)
defaultParameters.setProjectFilePath(testProject(relProjectFilePath));
defaultParameters.expandBuildConfiguration();
- ProjectResolver resolver(m_engine.get(), m_logger);
- return project = resolver.resolve(defaultParameters);
+ ProjectResolver resolver(defaultParameters, m_engine.get(), m_logger);
+ return project = resolver.resolve();
}
void TestLanguage::init()
{
+ // clear caches, otherwise StoredVariantValues may end up being at the same address
+ // as the destroyed value
+ m_engine->reset();
m_logSink->setLogLevel(LoggerInfo);
defaultParameters = {};
defaultParameters.setBuildRoot(m_tempDir.path() + "/buildroot");
defaultParameters.setPropertyCheckingMode(ErrorHandlingMode::Strict);
defaultParameters.setSettingsDirectory(m_settings->baseDirectory());
defaultParameters.setTopLevelProfile(profileName());
+ defaultParameters.setMaxJobCount(1);
defaultParameters.setConfigurationName("default");
defaultParameters.setEnvironment(QProcessEnvironment::systemEnvironment());
defaultParameters.setSearchPaths({SRCDIR "/../../../share/qbs"});
@@ -497,6 +522,29 @@ void TestLanguage::conditionalDepends()
QCOMPARE(exceptionCaught, false);
}
+void TestLanguage::convertStringList()
+{
+ FileContextPtr fileContext = FileContext::create();
+ fileContext->setFilePath("/dev/null");
+ JSSourceValueCreator sourceValueCreator(fileContext);
+ ItemPool pool;
+ Item *scope = Item::create(&pool, ItemType::Scope);
+ scope->setProperty("x", sourceValueCreator.create("[\"a\", \"b\"]"));
+
+ Evaluator evaluator(m_engine.get());
+ auto variantValue = evaluator.variantValue(scope, "x");
+ // despite we have a stringList prop, we evaluate it as a QVariantList
+ QCOMPARE(variantValue.userType(), QMetaType::Type::QVariantList);
+ // and we have to convert it explicitly
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ variantValue.convert(QMetaType(QMetaType::QStringList));
+#else
+ variantValue.convert(QMetaType::QStringList);
+#endif
+ QCOMPARE(variantValue.userType(), QMetaType::Type::QStringList);
+ QCOMPARE(variantValue, QStringList({"a", "b"}));
+}
+
void TestLanguage::delayedError()
{
QFETCH(bool, productEnabled);
@@ -691,7 +739,7 @@ void TestLanguage::enumerateProjectProperties()
auto products = productsFromProject(project);
QCOMPARE(products.size(), 1);
auto product = products.values().front();
- auto files = product->groups.front()->allFiles();
+ auto files = product->groups.front()->files;
QCOMPARE(product->groups.size(), size_t(1));
QCOMPARE(files.size(), size_t(1));
auto fileName = FileInfo::fileName(files.front()->absoluteFilePath);
@@ -854,6 +902,8 @@ void TestLanguage::erroneousFiles_data()
<< "Cyclic dependencies detected.";
QTest::newRow("dependency_cycle3")
<< "Cyclic dependencies detected.";
+ QTest::newRow("dependency_cycle3a")
+ << "Cyclic dependencies detected.";
QTest::newRow("dependency_cycle4")
<< "Cyclic dependencies detected.";
QTest::newRow("references_cycle")
@@ -906,10 +956,6 @@ void TestLanguage::erroneousFiles_data()
"cannot read property 'includes' of undefined";
QTest::newRow("misused-inherited-property") << "Binding to non-item property";
QTest::newRow("undeclared_property_in_Properties_item") << "Item 'blubb' is not declared";
- QTest::newRow("same-module-prefix1") << "The name of module 'prefix1' is equal to the first "
- "component of the name of module 'prefix1.suffix'";
- QTest::newRow("same-module-prefix2") << "The name of module 'prefix2' is equal to the first "
- "component of the name of module 'prefix2.suffix'";
QTest::newRow("conflicting-properties-in-export-items")
<< "Export item in inherited item redeclares property 'theProp' with different type.";
QTest::newRow("invalid-property-option")
@@ -967,6 +1013,10 @@ void TestLanguage::erroneousFiles_data()
<< "invalid-references.qbs:2:17.*Cannot open '.*nosuchproject.qbs'";
QTest::newRow("missing-js-file")
<< "missing-js-file-module.qbs.*Cannot open '.*javascriptfile.js'";
+ QTest::newRow("frozen-object") << "'key' is read-only";
+ QTest::newRow("frozen-object-list") << "object is not extensible";
+ QTest::newRow("module-property-binding-in-project")
+ << "Module properties cannot be set in Project items";
}
void TestLanguage::erroneousFiles()
@@ -1038,6 +1088,7 @@ void TestLanguage::exports()
product = products.value("B");
QVERIFY(!!product);
QVERIFY(product->dependencies.empty());
+ QCOMPARE(product->exportedModule.productDependencies, std::vector<QString>{"C"});
product = products.value("C");
QVERIFY(!!product);
QVERIFY(product->dependencies.empty());
@@ -1559,26 +1610,32 @@ void TestLanguage::invalidOverrides_data()
<< "products.MyOtherProduct.cpp.useRPaths" << QString();
}
-class JSSourceValueCreator
+void TestLanguage::invalidPropOnNonRequiredModule_data()
{
- FileContextPtr m_fileContext;
- std::vector<std::unique_ptr<QString>> m_strings;
-public:
- JSSourceValueCreator(const FileContextPtr &fileContext)
- : m_fileContext(fileContext)
- {
- }
+ QTest::addColumn<bool>("useExistingModule");
+ QTest::addColumn<bool>("errorExpected");
- JSSourceValuePtr create(const QString &sourceCode)
- {
- JSSourceValuePtr value = JSSourceValue::create();
- value->setFile(m_fileContext);
- auto str = std::make_unique<QString>(sourceCode);
- value->setSourceCode(*str.get());
- m_strings.push_back(std::move(str));
- return value;
+ QTest::newRow("existing module") << true << true;
+ QTest::newRow("non-existing module") << false << false;
+}
+
+void TestLanguage::invalidPropOnNonRequiredModule()
+{
+ QFETCH(bool, useExistingModule);
+ QFETCH(bool, errorExpected);
+
+ try {
+ defaultParameters.setOverriddenValues(
+ {std::make_pair("project.useExistingModule", useExistingModule)});
+ resolveProject("invalid-prop-on-non-required-module");
+ QVERIFY(!errorExpected);
+ } catch (const ErrorInfo &e) {
+ const QString errorString = e.toString();
+ QVERIFY2(errorExpected, qPrintable(errorString));
+ QVERIFY2(errorString.contains("Property 'nosuchprop' is not declared"),
+ qPrintable(errorString));
}
-};
+}
void TestLanguage::itemPrototype()
{
@@ -1672,6 +1729,24 @@ void TestLanguage::jsImportUsedInMultipleScopes()
QVERIFY(!exceptionCaught);
}
+void TestLanguage::localProfileAsTopLevelProfile()
+{
+ bool exceptionCaught = false;
+ try {
+ defaultParameters.setTopLevelProfile("test-profile");
+ resolveProject("local-profile-as-top-level-profile.qbs");
+ QVERIFY(!!project);
+ QCOMPARE(int(project->products.size()), 1);
+ const PropertyMapConstPtr &props = project->products.front()->moduleProperties;
+ QCOMPARE(props->qbsPropertyValue("architecture"), "arm");
+ QCOMPARE(props->qbsPropertyValue("targetPlatform"), "macos");
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
+ qDebug() << e.toString();
+ }
+ QCOMPARE(exceptionCaught, false);
+}
+
void TestLanguage::moduleMergingVariantValues()
{
bool exceptionCaught = false;
@@ -1686,6 +1761,185 @@ void TestLanguage::moduleMergingVariantValues()
QCOMPARE(exceptionCaught, false);
}
+void TestLanguage::moduleNameCollisions_data()
+{
+ QTest::addColumn<QString>("projectFile");
+ QTest::addColumn<bool>("collisionExpected");
+
+ QTest::newRow("simple collision (one order)") << "simple-collision1.qbs" << true;
+ QTest::newRow("simple collision (other order)") << "simple-collision2.qbs" << true;
+ QTest::newRow("collision with more components") << "complex-collision.qbs" << true;
+ QTest::newRow("no collision (same length)") << "no-collision1.qbs" << false;
+ QTest::newRow("no collision (different length)") << "no-collision2.qbs" << false;
+}
+
+void TestLanguage::moduleNameCollisions()
+{
+ QFETCH(QString, projectFile);
+ QFETCH(bool, collisionExpected);
+
+ try {
+ const QString compositeProjectFilePath = QString("module-name-collisions/") + projectFile;
+ QVERIFY(resolveProject(qPrintable(compositeProjectFilePath)));
+ QVERIFY(!collisionExpected);
+ } catch (const ErrorInfo &e) {
+ const QString errorString = e.toString();
+ QVERIFY2(collisionExpected, qPrintable(errorString));
+ QVERIFY2(errorString.contains("not allowed"), qPrintable(errorString));
+ }
+}
+
+void TestLanguage::moduleParameters_data()
+{
+ QTest::addColumn<QVariantMap>("inputProperties");
+ QTest::addColumn<QVariantMap>("expectedModuleParameters");
+ QTest::addColumn<bool>("errorExpected");
+
+ QTest::newRow("no overrides")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "false"),
+ std::make_pair("project.overrideFromExport", "false"),
+ std::make_pair("project.overrideFromProduct", "false")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})})}
+ << false;
+ QTest::newRow("override from product")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "false"),
+ std::make_pair("project.overrideFromExport", "false"),
+ std::make_pair("project.overrideFromProduct", "true")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})})}
+ << false;
+ QTest::newRow("override from export")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "false"),
+ std::make_pair("project.overrideFromExport", "true"),
+ std::make_pair("project.overrideFromProduct", "false")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromExportDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})})}
+ << false;
+ QTest::newRow("override from export and product")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "false"),
+ std::make_pair("project.overrideFromExport", "true"),
+ std::make_pair("project.overrideFromProduct", "true")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})})}
+ << false;
+ QTest::newRow("override from module")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "true"),
+ std::make_pair("project.overrideFromExport", "false"),
+ std::make_pair("project.overrideFromProduct", "false")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromModuleDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})})}
+ << false;
+ QTest::newRow("override from module and product")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "true"),
+ std::make_pair("project.overrideFromExport", "false"),
+ std::make_pair("project.overrideFromProduct", "true")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})})}
+ << false;
+ QTest::newRow("override from module and export")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "true"),
+ std::make_pair("project.overrideFromExport", "true"),
+ std::make_pair("project.overrideFromProduct", "false")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromExportDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromParameters")})})}
+ << true;
+ QTest::newRow("override from module, export and product")
+ << QVariantMap{
+ std::make_pair("project.overrideFromModule", "true"),
+ std::make_pair("project.overrideFromExport", "true"),
+ std::make_pair("project.overrideFromProduct", "true")}
+ << QVariantMap{
+ std::make_pair("higher",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})}),
+ std::make_pair("dep",
+ QVariantMap{std::make_pair("lower",
+ QVariantMap{std::make_pair("param", "fromProductDepends")})})}
+ << false;
+}
+
+void TestLanguage::moduleParameters()
+{
+ QFETCH(QVariantMap, inputProperties);
+ QFETCH(QVariantMap, expectedModuleParameters);
+ QFETCH(bool, errorExpected);
+
+ try {
+ defaultParameters.setOverriddenValues(inputProperties);
+ resolveProject("module-parameters/module-parameters.qbs");
+ QVERIFY(!errorExpected);
+ QVERIFY(project);
+ QCOMPARE(int(project->products.size()), 2);
+ const ResolvedProductPtr mainProduct = productsFromProject(project).value("main");
+ QVERIFY(mainProduct);
+ QCOMPARE(int(mainProduct->moduleParameters.size()), 2);
+ for (auto it = expectedModuleParameters.cbegin(); it != expectedModuleParameters.cend();
+ ++it) {
+ const auto findInProduct = [&](const QString &moduleName) {
+ for (auto it = mainProduct->moduleParameters.cbegin();
+ it != mainProduct->moduleParameters.cend(); ++it) {
+ if (it.key()->name == moduleName)
+ return it.value();
+ }
+ return QVariantMap();
+ };
+ const QVariantMap actual = findInProduct(it.key());
+ const QVariantMap expected = it.value().toMap();
+ const bool same = qVariantMapsEqual(actual, expected);
+ if (!same) {
+ qDebug().noquote() << "---" << expected;
+ qDebug().noquote() << "+++" << actual;
+ }
+ QVERIFY(same);
+ }
+ } catch (const ErrorInfo &e) {
+ QVERIFY2(errorExpected, qPrintable(e.toString()));
+ }
+}
+
void TestLanguage::modulePrioritizationBySearchPath_data()
{
QTest::addColumn<QStringList>("searchPaths");
@@ -2402,6 +2656,35 @@ void TestLanguage::pathProperties()
QCOMPARE(exceptionCaught, false);
}
+void TestLanguage::probesAndMultiplexing()
+{
+ bool exceptionCaught = false;
+ try {
+ resolveProject("probes-and-multiplexing.qbs");
+ QVERIFY(project);
+ QCOMPARE(int(project->products.size()), 3);
+ QStringList architectures{"x86", "x86_64", "arm"};
+ for (const ResolvedProductPtr &product : project->products) {
+ const QString arch = product->moduleProperties->moduleProperty("qbs", "architecture")
+ .toString();
+ QVERIFY2(architectures.removeOne(arch), qPrintable(arch));
+ QCOMPARE(product->productProperties.value("archFromProbe").toString(), arch);
+ bool foundGroup = false;
+ for (const GroupPtr &group : product->groups) {
+ if (group->name == "theGroup") {
+ foundGroup = true;
+ QCOMPARE(group->properties->moduleProperty("qbs", "sysroot"), "/" + arch);
+ }
+ }
+ QVERIFY(foundGroup);
+ }
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
+ qDebug() << e.toString();
+ }
+ QCOMPARE(exceptionCaught, false);
+}
+
void TestLanguage::profileValuesAndOverriddenValues()
{
bool exceptionCaught = false;
@@ -2760,6 +3043,7 @@ void TestLanguage::qbsPropertiesInProjectCondition()
try {
resolveProject("qbs-properties-in-project-condition.qbs");
QVERIFY(!!project);
+ QVERIFY(!project->enabled);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 0);
} catch (const ErrorInfo &e) {
@@ -2816,7 +3100,7 @@ void TestLanguage::relaxedErrorMode()
QVERIFY(missingFile->enabled);
QCOMPARE(missingFile->groups.size(), size_t(1));
QVERIFY(missingFile->groups.front()->enabled);
- QCOMPARE(missingFile->groups.front()->allFiles().size(), size_t(2));
+ QCOMPARE(missingFile->groups.front()->files.size(), size_t(2));
const ResolvedProductConstPtr fine = productMap.value("fine");
QVERIFY(fine->enabled);
QCOMPARE(fine->allFiles().size(), size_t(1));
@@ -3212,10 +3496,10 @@ void TestLanguage::wildcards()
group = product->groups.front();
}
QVERIFY(!!group);
- QCOMPARE(group->files.size(), size_t(0));
+ QCOMPARE(group->files.size(), expected.size()); // we assume all files are wildcards
QVERIFY(!!group->wildcards);
QStringList actualFilePaths;
- for (const SourceArtifactPtr &artifact : group->wildcards->files) {
+ for (const SourceArtifactPtr &artifact : group->files) {
QString str = artifact->absoluteFilePath;
int idx = str.indexOf(m_wildcardsTestDirPath);
if (idx != -1)
@@ -3239,4 +3523,3 @@ int main(int argc, char *argv[])
TestLanguage tl(ConsoleLogger::instance().logSink(), s.get());
return QTest::qExec(&tl, argc, argv);
}
-
diff --git a/tests/auto/language/tst_language.h b/tests/auto/language/tst_language.h
index 3820e3f7c..870b7d1f8 100644
--- a/tests/auto/language/tst_language.h
+++ b/tests/auto/language/tst_language.h
@@ -71,6 +71,7 @@ private slots:
void chainedProbes();
void canonicalArchitecture();
void conditionalDepends();
+ void convertStringList();
void delayedError();
void delayedError_data();
void dependencyOnAllProfiles();
@@ -109,12 +110,19 @@ private slots:
void invalidBindingInDisabledItem();
void invalidOverrides();
void invalidOverrides_data();
+ void invalidPropOnNonRequiredModule_data();
+ void invalidPropOnNonRequiredModule();
void itemPrototype();
void itemScope();
void jsExtensions();
void jsImportUsedInMultipleScopes_data();
void jsImportUsedInMultipleScopes();
+ void localProfileAsTopLevelProfile();
void moduleMergingVariantValues();
+ void moduleNameCollisions_data();
+ void moduleNameCollisions();
+ void moduleParameters_data();
+ void moduleParameters();
void modulePrioritizationBySearchPath_data();
void modulePrioritizationBySearchPath();
void moduleProperties_data();
@@ -138,6 +146,7 @@ private slots:
void overriddenVariantProperty();
void parameterTypes();
void pathProperties();
+ void probesAndMultiplexing();
void productConditions();
void productDirectories();
void profileValuesAndOverriddenValues();
diff --git a/tests/auto/pkgconfig/testdata/lib/pkgconfig/prefix.pc b/tests/auto/pkgconfig/testdata/lib/pkgconfig/prefix.pc
new file mode 100644
index 000000000..64b980803
--- /dev/null
+++ b/tests/auto/pkgconfig/testdata/lib/pkgconfig/prefix.pc
@@ -0,0 +1,13 @@
+prefix=/usr
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=/usr/include
+usrdir=/usrdir
+
+Name: Prefix test
+Description: This tests prefix auto detection
+Version: 1.0.0
+Requires:
+Libs: -lprefix
+Libs.private: -lm
+Cflags: -I${includedir}
diff --git a/tests/auto/pkgconfig/testdata/requires-test-merged-static.json b/tests/auto/pkgconfig/testdata/requires-test-merged-static.json
deleted file mode 100644
index 2c43b2d40..000000000
--- a/tests/auto/pkgconfig/testdata/requires-test-merged-static.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "Name": "Requires test package",
- "Description": "Dummy pkgconfig test package for testing Requires/Requires.private",
- "Version": "1.0.0",
- "Libs": [
- {"Type": "LibraryPath", "Value": "/public-dep/lib"},
- {"Type": "LibraryName", "Value": "public-dep"},
- {"Type": "LibraryPath", "Value": "/private-dep/lib"},
- {"Type": "LibraryName", "Value": "private-dep"},
- {"Type": "LibraryPath", "Value": "/requires-test/lib"},
- {"Type": "LibraryName", "Value": "requires-test"}
- ],
- "Cflags": [
- {"Type": "IncludePath", "Value": "/public-dep/include"},
- {"Type": "IncludePath", "Value": "/private-dep/include"},
- {"Type": "IncludePath", "Value": "/requires-test/include"}
- ],
- "Requires": [
- ],
- "RequiresPrivate": [
- ]
-}
diff --git a/tests/auto/pkgconfig/testdata/requires-test-merged.json b/tests/auto/pkgconfig/testdata/requires-test-merged.json
deleted file mode 100644
index 88114ba30..000000000
--- a/tests/auto/pkgconfig/testdata/requires-test-merged.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "Name": "Requires test package",
- "Description": "Dummy pkgconfig test package for testing Requires/Requires.private",
- "Version": "1.0.0",
- "Libs": [
- {"Type": "LibraryPath", "Value": "/public-dep/lib"},
- {"Type": "LibraryName", "Value": "public-dep"},
- {"Type": "LibraryPath", "Value": "/requires-test/lib"},
- {"Type": "LibraryName", "Value": "requires-test"}
- ],
- "Cflags": [
- {"Type": "IncludePath", "Value": "/public-dep/include"},
- {"Type": "IncludePath", "Value": "/requires-test/include"}
- ],
- "Requires": [
- ],
- "RequiresPrivate": [
- ]
-}
diff --git a/tests/auto/pkgconfig/tst_pkgconfig.cpp b/tests/auto/pkgconfig/tst_pkgconfig.cpp
index 220e54e7a..86d954259 100644
--- a/tests/auto/pkgconfig/tst_pkgconfig.cpp
+++ b/tests/auto/pkgconfig/tst_pkgconfig.cpp
@@ -33,6 +33,7 @@
#include <tools/fileinfo.h>
#include <tools/hostosinfo.h>
#include <pkgconfig.h>
+#include <pcparser.h>
#include <jsextensions/pkgconfigjs.h>
#include <QJsonArray>
@@ -58,6 +59,48 @@ void TestPkgConfig::initTestCase()
qPrintable(errorMessage));
}
+void TestPkgConfig::fileName()
+{
+ QCOMPARE(qbs::Internal::fileName(""), "");
+ QCOMPARE(qbs::Internal::fileName("file.txt"), "file.txt");
+ QCOMPARE(qbs::Internal::fileName("/home/user/file.txt"), "file.txt");
+ QCOMPARE(qbs::Internal::fileName("/"), "");
+#if defined(Q_OS_WIN)
+ QCOMPARE(qbs::Internal::fileName("c:file.txt"), "file.txt");
+ QCOMPARE(qbs::Internal::fileName("c:"), "");
+#endif
+}
+
+void TestPkgConfig::completeBaseName()
+{
+ QCOMPARE(qbs::Internal::completeBaseName(""), "");
+ QCOMPARE(qbs::Internal::completeBaseName("file.txt"), "file");
+ QCOMPARE(qbs::Internal::completeBaseName("archive.tar.gz"), "archive.tar");
+ QCOMPARE(qbs::Internal::completeBaseName("/home/user/file.txt"), "file");
+#if defined(Q_OS_WIN)
+ QCOMPARE(qbs::Internal::completeBaseName("c:file.txt"), "file");
+ QCOMPARE(qbs::Internal::completeBaseName("c:archive.tar.gz"), "archive.tar");
+ QCOMPARE(qbs::Internal::completeBaseName("c:"), "");
+#endif
+}
+
+void TestPkgConfig::parentPath()
+{
+ QCOMPARE(qbs::Internal::parentPath(""), "");
+ QCOMPARE(qbs::Internal::parentPath("file.txt"), ".");
+ QCOMPARE(qbs::Internal::parentPath("/home/user/file.txt"), "/home/user");
+ QCOMPARE(qbs::Internal::parentPath("/home/user/"), "/home/user");
+ QCOMPARE(qbs::Internal::parentPath("/home"), "/");
+ QCOMPARE(qbs::Internal::parentPath("/"), "/");
+#if defined(Q_OS_WIN)
+ QCOMPARE(qbs::Internal::parentPath("c:/folder/file.txt"), "c:/folder");
+ QCOMPARE(qbs::Internal::parentPath("c:/folder/"), "c:/folder");
+ QCOMPARE(qbs::Internal::parentPath("c:/folder"), "c:/");
+ QCOMPARE(qbs::Internal::parentPath("c:/"), "c:/");
+ QCOMPARE(qbs::Internal::parentPath("c:"), "c:");
+#endif
+}
+
void TestPkgConfig::pkgConfig()
{
QFETCH(QString, pcFileName);
@@ -67,9 +110,6 @@ void TestPkgConfig::pkgConfig()
if (jsonFileName.isEmpty())
jsonFileName = pcFileName;
- if (!optionsMap.contains("mergeDependencies"))
- optionsMap["mergeDependencies"] = false;
-
Options options = qbs::Internal::PkgConfigJs::convertOptions(
QProcessEnvironment::systemEnvironment(), optionsMap);
options.libDirs.push_back(m_workingDataDir.toStdString());
@@ -176,14 +216,6 @@ void TestPkgConfig::pkgConfig_data()
<< QStringLiteral("simple") << QString() << QVariantMap();
QTest::newRow("requires-test")
<< QStringLiteral("requires-test") << QString() << QVariantMap();
- QTest::newRow("requires-test-merged")
- << QStringLiteral("requires-test")
- << QStringLiteral("requires-test-merged")
- << QVariantMap({{"mergeDependencies", true}});
- QTest::newRow("requires-test-merged-static")
- << QStringLiteral("requires-test")
- << QStringLiteral("requires-test-merged-static")
- << QVariantMap({{"mergeDependencies", true}, {"staticMode", true}});
QTest::newRow("special-flags")
<< QStringLiteral("special-flags") << QString() << QVariantMap();
QTest::newRow("system")
@@ -194,10 +226,6 @@ void TestPkgConfig::pkgConfig_data()
<< QStringLiteral("tilde") << QString() << QVariantMap();
QTest::newRow("variables")
<< QStringLiteral("variables") << QString() << QVariantMap();
- QTest::newRow("variables-merged")
- << QStringLiteral("variables")
- << QString()
- << QVariantMap({{"mergeDependencies", true}});
QTest::newRow("whitespace")
<< QStringLiteral("whitespace") << QString() << QVariantMap();
QTest::newRow("base.name")
@@ -214,4 +242,25 @@ void TestPkgConfig::benchSystem()
}
}
+void TestPkgConfig::prefix()
+{
+ const auto prefixDir = m_workingDataDir;
+ const auto libDir = m_workingDataDir + "/lib";
+ const auto includeDir = m_workingDataDir + "/include";
+ const auto pkgconfigDir = libDir + "/pkgconfig";
+ Options options = qbs::Internal::PkgConfigJs::convertOptions(
+ QProcessEnvironment::systemEnvironment(), {});
+ options.definePrefix = true;
+ options.libDirs.push_back(pkgconfigDir.toStdString());
+ PkgConfig pkgConfig(std::move(options));
+ const auto &packageOr = pkgConfig.getPackage("prefix");
+ QVERIFY(packageOr.isValid());
+ const auto &package = packageOr.asPackage();
+ QCOMPARE(package.variables.at("prefix"), prefixDir.toStdString());
+ QCOMPARE(package.variables.at("exec_prefix"), prefixDir.toStdString());
+ QCOMPARE(package.variables.at("libdir"), libDir.toStdString());
+ QCOMPARE(package.variables.at("includedir"), includeDir.toStdString());
+ QCOMPARE(package.variables.at("usrdir"), "/usrdir");
+}
+
QTEST_MAIN(TestPkgConfig)
diff --git a/tests/auto/pkgconfig/tst_pkgconfig.h b/tests/auto/pkgconfig/tst_pkgconfig.h
index 687411862..47654e1ec 100644
--- a/tests/auto/pkgconfig/tst_pkgconfig.h
+++ b/tests/auto/pkgconfig/tst_pkgconfig.h
@@ -41,9 +41,13 @@ public:
private slots:
void initTestCase();
+ void fileName();
+ void completeBaseName();
+ void parentPath();
void pkgConfig();
void pkgConfig_data();
void benchSystem();
+ void prefix();
private:
const QString m_sourceDataDir;
diff --git a/tests/clang-format-test/CMakeLists.txt b/tests/clang-format-test/CMakeLists.txt
new file mode 100644
index 000000000..e84b539a7
--- /dev/null
+++ b/tests/clang-format-test/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_qbs_app(clang-format-test
+ DEPENDS Qt${QT_VERSION_MAJOR}::Core
+ SOURCES clang-format.cpp
+)
diff --git a/tests/clang-format-test/clang-format-test.qbs b/tests/clang-format-test/clang-format-test.qbs
new file mode 100644
index 000000000..3c34b5962
--- /dev/null
+++ b/tests/clang-format-test/clang-format-test.qbs
@@ -0,0 +1,5 @@
+QtApplication {
+ Depends { name: "Qt.core" }
+ files: "clang-format.cpp"
+ cpp.warningLevel: "none"
+}
diff --git a/tests/clang-format-test/clang-format.cpp b/tests/clang-format-test/clang-format.cpp
new file mode 100644
index 000000000..3d952f11c
--- /dev/null
+++ b/tests/clang-format-test/clang-format.cpp
@@ -0,0 +1,681 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+//
+// KEEP THIS FILE COMPILABLE TO ENSURE THAT WE TEST AGAINST VALID CODE.
+//
+
+// NOTE: Nice, includes are sorted
+#include <QCoreApplication>
+#include <QFile>
+#include <QLoggingCategory>
+#include <QObject>
+#include <QVector>
+
+#include <functional>
+#include <iostream>
+#include <vector>
+
+static int aGlobalInt = 3;
+
+// -------------------------------------------------------------------------------------------------
+// Preprocessor
+// -------------------------------------------------------------------------------------------------
+
+// NOTE: Ops, preprocessor branches are not indented ("#define" vs "# define")
+#if defined(TEXTEDITOR_LIBRARY)
+#define TEXTEDITOR_EXPORT Q_DECL_EXPORT
+#else
+#define TEXTEDITOR_EXPORT Q_DECL_IMPORT
+#endif
+
+// -------------------------------------------------------------------------------------------------
+// Macros
+// -------------------------------------------------------------------------------------------------
+
+#define QTCREATOR_UTILS_EXPORT
+
+// qtcassert.h:
+namespace Utils {
+void writeAssertLocation(const char *msg)
+{
+ std::printf("%s", msg);
+}
+} // namespace Utils
+
+#define QTC_ASSERT_STRINGIFY_HELPER(x) #x
+#define QTC_ASSERT_STRINGIFY(x) QTC_ASSERT_STRINGIFY_HELPER(x)
+#define QTC_ASSERT_STRING(cond) \
+ ::Utils::writeAssertLocation("\"" cond "\" in file " __FILE__ \
+ ", line " QTC_ASSERT_STRINGIFY(__LINE__))
+
+// NOTE: Ops, macro definitions: are more verbose
+#define QTC_ASSERT(cond, action) \
+ if (cond) { \
+ } else { \
+ QTC_ASSERT_STRING(#cond); \
+ action; \
+ } \
+ do { \
+ } while (0)
+#define QTC_CHECK(cond) \
+ if (cond) { \
+ } else { \
+ QTC_ASSERT_STRING(#cond); \
+ } \
+ do { \
+ } while (0)
+#define QTC_GUARD(cond) ((cond) ? true : (QTC_ASSERT_STRING(#cond), false))
+
+void lala(int foo)
+{
+ Q_UNUSED(foo)
+ Q_UNUSED(foo);
+ QTC_ASSERT(true, return); // NOTE: Ops, extra space with QTC_ASSERT macro and return keyword
+ QTC_ASSERT(true, return;);
+ while (true)
+ QTC_ASSERT(true, break); // ...but this is fine
+}
+
+// -------------------------------------------------------------------------------------------------
+// Namespaces
+// -------------------------------------------------------------------------------------------------
+
+namespace N {
+namespace C {
+
+struct Foo
+{};
+
+struct Bar
+{};
+
+namespace {
+class ClassInUnnamedNamespace
+{};
+} // namespace
+
+// NOTE: Nice, namespace end comments are added/updated automatically
+} // namespace C
+
+struct Baz
+{};
+
+} // namespace N
+namespace N2 {}
+
+// -------------------------------------------------------------------------------------------------
+// Forward declarations
+// -------------------------------------------------------------------------------------------------
+
+// NOTE: Ops, no one-liner anymore: forward declarations within namespace
+namespace N {
+struct Baz;
+}
+
+class SourceLocation;
+class SourceRange;
+class ClangString;
+
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
+QT_FORWARD_DECLARE_CLASS(QTextDocument)
+QT_FORWARD_DECLARE_CLASS(QTextBlock);
+
+// -------------------------------------------------------------------------------------------------
+// Using declarations, using directives, using alias, typedefs
+// -------------------------------------------------------------------------------------------------
+
+// NOTE: OK, using directives are not sorted
+using namespace std;
+using namespace N2;
+using namespace N;
+
+// NOTE: Nice, using declarations are sorted
+using N::C::Bar;
+using N::C::Foo;
+
+// NOTE: OK, typedefs are not sorted
+typedef N::C::Foo TypedefedFoor;
+typedef N::C::Bar TypedefedBar;
+
+// NOTE: OK, using aliases are not sorted
+using AliasedFoor = N::C::Foo;
+using AliasedBar = N::C::Bar;
+
+// -------------------------------------------------------------------------------------------------
+// Comments
+// -------------------------------------------------------------------------------------------------
+
+// NOTE: OK, reflowing/wrapping of comments is turned off for now.
+// This is a fancy comment that might be reflowed or not or not or not or not or not or not or not or not or not.
+
+// NOTE: Nice, comments after declarations are aligned
+int foo; // fancy comment describing foo
+int superDuperFoo; // fancy comment describing superDuperFoo
+int lalaFoo; // fancy comment describing lalaFoo
+
+// -------------------------------------------------------------------------------------------------
+// Function declarations
+// -------------------------------------------------------------------------------------------------
+
+void f();
+
+void f2() {}
+
+void f3(int parameter1, int parameter2, int parameter3);
+
+// NOTE: Ops, awkward placement of parameter list, there is no equivalent of
+// PenaltyBreakBeforeFirstCallParameter for parameter list
+void f3(
+ int parameter1, int parameter2, int parameter3, int parameter4, int parameter5, int parameter6);
+
+void f3(
+ int parameter1,
+ int parameter2,
+ int parameter3,
+ int parameter4,
+ int parameter5,
+ int parameter6,
+ int parrameter7,
+ int p = aGlobalInt);
+
+bool operator==(N::Baz, N::Baz);
+
+template<class T>
+void fancyTemplateFunction();
+
+template<typename... Type>
+void variadicTemplateFunction(Type *...arg);
+
+// -------------------------------------------------------------------------------------------------
+// Inside functions
+// -------------------------------------------------------------------------------------------------
+
+int myfunction(int parameter1, int parameter2)
+{
+ // Function calls
+ myfunction(parameter1, parameter2);
+
+ // Casts
+ int value = 3;
+ int z = (int) value;
+ int a = static_cast<int>(value + z);
+
+ // Pointer/references alignment
+ int *p = nullptr;
+ int &r = *p;
+
+ // Operators
+ int result = parameter1 + parameter1 + parameter1 + parameter1 + parameter1 + parameter1
+ + parameter1 + parameter1;
+
+ // Long expressions
+ int someVeryLooooooooooooooooooooooooooooooooooooooooooooooooooongInt = -1;
+ bool condition1 = false;
+ bool condition2 = false;
+ // NOTE: Ops, alignment of readable, boolean and complex expressions will be reverted
+ if (condition1 || condition2
+ || someVeryLooooooooooooooooooooooooooooooooooooooooooooooooooongInt) {
+ return value;
+ }
+
+ // Initializer lists
+ vector<int> x{1, 2, 3, 4};
+ vector<Foo> y{{}, {}, {}, {}};
+ new int[3]{1, 2, 3};
+
+ // Streams
+ // NOTE: OK, there is a heuristic (endl, '\n') to wrap statements with stream operators.
+ cout << "Hello" << parameter1 << endl
+ << parameter2 << endl
+ << result << endl
+ << condition1 << someVeryLooooooooooooooooooooooooooooooooooooooooooooooooooongInt;
+ cout << "Hello" << parameter1 << '\n'
+ << parameter2 << '\n'
+ << a << '\n'
+ << condition1 << someVeryLooooooooooooooooooooooooooooooooooooooooooooooooooongInt;
+ // ...but here not:
+ cout << "Hello" << parameter1 << '\t' << parameter2 << '\t' << z << '\t' << condition1
+ << someVeryLooooooooooooooooooooooooooooooooooooooooooooooooooongInt << r;
+
+ return 1;
+}
+
+// -------------------------------------------------------------------------------------------------
+// Ternary Operator
+// -------------------------------------------------------------------------------------------------
+
+bool ternary()
+{
+ bool someWhatLongerName;
+ bool someWhatLongerName2;
+ bool isThatReallyReallyReallyReallyReallyReallyReallyReallyTrue = false;
+ bool isThatReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyTrue = false;
+
+ bool yesno = true ? true : false;
+ bool sino = isThatReallyReallyReallyReallyReallyReallyReallyReallyTrue ? someWhatLongerName
+ : someWhatLongerName2;
+ bool danet = isThatReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyTrue
+ ? someWhatLongerName
+ : someWhatLongerName2;
+
+ return yesno && sino && danet;
+}
+
+// -------------------------------------------------------------------------------------------------
+// Penalty Test
+// -------------------------------------------------------------------------------------------------
+
+int functionToCall(int parameter1)
+{
+ return 1;
+}
+
+int functionToCall(int paramter1, int parameter2)
+{
+ return 1;
+}
+
+int functionToCall(int paramter1, int parameter2, int parameter3)
+{
+ return 1;
+}
+
+int functionToCall(int paramter1, int parameter2, int parameter3, int parameter4, int parameter5)
+{
+ return 1;
+}
+
+int functionToCall(
+ int paramter1, int parameter2, int parameter3, int parameter4, int parameter5, int paramete6)
+{
+ return 1;
+}
+
+int functionToCall(
+ int paramter1,
+ int parameter2,
+ int parameter3,
+ int parameter4,
+ int parameter5,
+ int paramete6,
+ int parameter6)
+{
+ return 1;
+}
+
+bool functionToCallSt(const QString &paramter1, const QStringList &list)
+{
+ return 1;
+}
+
+void emitAddOutput(const QString &text, int priority) {}
+void emitAddOutput(const QString &text, int priority, int category) {}
+
+void penaltyTests(bool isThatTrue)
+{
+ int valueX = 3;
+ int valueY = 1;
+ int valueZ = 1;
+ int valueXTimesY = valueX * valueY;
+ int unbelievableBigValue = 1000000;
+ int unlimitedValueunbelievableBigValue = 1000000;
+ QString arguments;
+ QString argumentsVeryLong;
+
+ const QFile::OpenMode openMode = isThatTrue ? QIODevice::ReadOnly
+ : (QIODevice::ReadOnly | QIODevice::Text);
+
+ const auto someValue10 = functionToCall(valueX, valueY, valueXTimesY);
+ const auto someValue11 = functionToCall(
+ valueX, valueY, valueXTimesY, unbelievableBigValue, unbelievableBigValue);
+ const auto someValue12 = functionToCall(
+ valueX,
+ valueY,
+ valueXTimesY,
+ unbelievableBigValue,
+ unbelievableBigValue * unbelievableBigValue,
+ unbelievableBigValue);
+
+ const auto someValue13 = functionToCall(
+ valueX,
+ valueY,
+ valueXTimesY,
+ unbelievableBigValue,
+ functionToCall(functionToCall(valueX), functionToCall(valueY)),
+ unbelievableBigValue);
+
+ const auto someValue14WithAnOutstandingLongName = functionToCall(
+ valueX,
+ valueY,
+ valueXTimesY,
+ unbelievableBigValue,
+ functionToCall(functionToCall(valueX), functionToCall(valueY)),
+ unbelievableBigValue);
+
+ const bool someValue20 = functionToCall(valueX, valueY, valueXTimesY) || functionToCall(3);
+ const bool someValue21 = functionToCall(valueX, valueY, valueXTimesY)
+ || functionToCall(valueX, valueY);
+
+ emitAddOutput(
+ QCoreApplication::tr("Starting: \"%1\" %2")
+ .arg("/some/very/very/very/very/long/path/to/an/executable", arguments),
+ functionToCall(3),
+ functionToCall(3) | valueX);
+
+ emitAddOutput(
+ QCoreApplication::tr("Starting: \"%1\" %2")
+ .arg("/some/very/very/very/very/long/path/to/an/executable", argumentsVeryLong),
+ functionToCall(3),
+ functionToCall(3) | unlimitedValueunbelievableBigValue
+ | unlimitedValueunbelievableBigValue);
+
+ const QString path;
+ const bool someLongerNameNNNNNNNNNN = functionToCallSt(
+ path, QStringList(QLatin1String("-print-env")));
+}
+
+// -------------------------------------------------------------------------------------------------
+// Classes and member functions
+// -------------------------------------------------------------------------------------------------
+
+class Base1
+{};
+class Base2
+{};
+
+class MyClass : public QObject, public Base1, public Base2
+{
+ Q_OBJECT
+
+public:
+ friend class FriendX; // for X::foo()
+ friend class OtherFriendY; // for Y::bar()
+
+ MyClass() {}
+
+ MyClass(int d1)
+ : data1(d1)
+ {}
+
+ MyClass(int d1, int d2)
+ : data1(d1)
+ , data2(d2)
+ , data3(d2)
+ {}
+
+ MyClass(
+ int initialData1,
+ int initialData2,
+ int initialData3,
+ int initialData4,
+ int initialData5,
+ int initialData6)
+ : data1(initialData1)
+ , data2(initialData2)
+ , data3(initialData3)
+ , data4(initialData4)
+ , data5(initialData5)
+ , data6(initialData6)
+ {
+ // NOTE: Ops, manual alignment of QObject::connect() arguments is reverted
+ // (we tend to write emitter/receiver on separate lines)
+ connect(this, &MyClass::inlineZeroStatements, this, &MyClass::nonInlineSingleStatement);
+ }
+
+ void inlineZeroStatements() {}
+ void inlineSingleStatement() { aGlobalInt = 2; }
+ void inlineMultipleStatements()
+ {
+ aGlobalInt = 2;
+ aGlobalInt = 3;
+ }
+
+ void nonInlineZeroStatements();
+ void nonInlineSingleStatement();
+ void nonInlineMultipleStatements();
+
+ virtual void shortVirtual(int parameter1, int parameter2, int parameter3) = 0;
+
+ // NOTE: Ops, awkward placement of "= 0;"
+ virtual void longerVirtual(
+ int parameter1, int parameter2, int parameter3, int parameter4, int parameter5)
+ = 0;
+
+ int someGetter() const;
+
+private:
+ int data1 = 0;
+ int data2 = 0;
+ int data3 = 0;
+ int data4 = 0;
+ int data5 = 0;
+ int data6 = 0;
+};
+
+void MyClass::nonInlineZeroStatements() {}
+
+void MyClass::nonInlineSingleStatement()
+{
+ aGlobalInt = 2;
+}
+
+void MyClass::nonInlineMultipleStatements()
+{
+ aGlobalInt = 2;
+ aGlobalInt = 2;
+}
+
+template<class T>
+class TemplateClass
+{};
+
+// -------------------------------------------------------------------------------------------------
+// Enums
+// -------------------------------------------------------------------------------------------------
+
+// NOTE: OK, enums: one-liner are possible
+enum MyEnum { SourceFiles = 0x1, GeneratedFiles = 0x2, AllFiles = SourceFiles | GeneratedFiles };
+
+// NOTE: OK, enums: a comment breaks one-liners
+enum MyEnumX {
+ OSourceFiles = 0x1 // fancy
+};
+
+// NOTE: OK, enums: longer ones are wrapped
+enum MyOtherEnum {
+ XSourceFiles = 0x1,
+ XGeneratedFiles = 0x2,
+ XAllFiles = XSourceFiles | XGeneratedFiles
+};
+
+enum {
+ Match_None,
+ Match_TooManyArgs,
+ Match_TooFewArgs,
+ Match_Ok
+} matchType
+ = Match_None; // NOTE: Ops, awkward placement of "= value;" at enum definition+use
+
+// -------------------------------------------------------------------------------------------------
+// Lambdas
+// -------------------------------------------------------------------------------------------------
+
+void takeLambda(std::function<int()>) {}
+void takeLambdaWithSomeLongerNameHa(std::function<int()>) {}
+bool UtilsFiltered(QVector<N::Baz>, std::function<int(const N::Baz &)>)
+{
+ return true;
+}
+
+void lambda()
+{
+ auto shortLambda = []() { return 1; };
+ auto longLambda = []() {
+ aGlobalInt = 3;
+ return;
+ };
+
+ takeLambda([]() { return true; });
+ takeLambda([]() {
+ aGlobalInt = 3;
+ return aGlobalInt;
+ });
+
+ // NOTE: Ops, lambda: capture and parameter list should be on separate line
+ int thisCouldBeSomeLongerFunctionCallExpressionOrSoSoSo;
+ takeLambdaWithSomeLongerNameHa(
+ [&]() { return thisCouldBeSomeLongerFunctionCallExpressionOrSoSoSo; });
+
+ QVector<N::Baz> myClasses;
+ UtilsFiltered(myClasses, [](const N::Baz &) { return 1; });
+
+ // NOTE: Ops, lambda: lambda not started on new line in if-condition
+ if (UtilsFiltered(myClasses, [](const N::Baz &) { return 1; })) {
+ ++aGlobalInt;
+ }
+}
+
+// -------------------------------------------------------------------------------------------------
+// Control flow
+// -------------------------------------------------------------------------------------------------
+
+int fn(int, int, int)
+{
+ return 1;
+}
+
+void controlFlow(int x)
+{
+ int y = -1;
+
+ // if
+ if (true)
+ fn(1, 2, 3);
+
+ int value = 3;
+ if (value) {
+ value += value + 1;
+ --value;
+ }
+
+ if (x == y)
+ fn(1, 2, 3);
+ else if (x > y)
+ fn(1, 2, 3);
+ else
+ fn(1, 2, 3);
+
+ if (x == y) {
+ fn(1, 2, 3);
+ return;
+ } else if (x > y) {
+ fn(1, 2, 3);
+ } else {
+ fn(1, 2, 3);
+ }
+
+ // switch
+ switch (x) {
+ case 0:
+ ++aGlobalInt;
+ return;
+ case 1:
+ fn(1, 2, 3);
+ ++aGlobalInt;
+ break;
+ case 2:
+ fn(1, 2, 3);
+ break;
+ default:
+ break;
+ }
+
+ // do-while
+ do {
+ value += value + 1;
+ ++value;
+ } while (true);
+
+ // for
+ QVector<int> myVector;
+ for (int i = 0; i < myVector.size(); ++i)
+ ++aGlobalInt;
+
+ for (QVector<int>::const_iterator i = myVector.begin(); i != myVector.end(); ++i)
+ ++aGlobalInt;
+
+ QVector<int> myVectorWithLongName;
+ for (QVector<int>::const_iterator i = myVectorWithLongName.begin(),
+ ei = myVectorWithLongName.end();
+ i != ei;
+ ++i) {
+ }
+
+ forever {
+ ++aGlobalInt;
+ --aGlobalInt;
+ }
+}
+
+// -------------------------------------------------------------------------------------------------
+// Function declarations and calls - extreme cases
+// -------------------------------------------------------------------------------------------------
+
+void extremeFunction(const char[]) {}
+void extremeFunction(
+ int uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunbelievableLongParameter)
+{
+ extremeFunction(
+ uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunbelievableLongParameter);
+
+ int uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunbelievableLongValue
+ = 3;
+ ++uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuunbelievableLongValue;
+
+ extremeFunction(
+ "some super duper super duper super duper super duper super duper super duper long");
+
+ // BreakStringLiterals splits the string.
+ extremeFunction("some super duper super duper super duper super duper super duper super duper "
+ "super duper long");
+}
+
+void extremeFunction2(
+ int parameter1,
+ int parameter2,
+ int parameter3WithAVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVerVeryVeryLong)
+{
+ extremeFunction2(
+ parameter1,
+ parameter2,
+ parameter3WithAVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVerVeryVeryLong);
+}
+
+void extremeFunction3(
+ int parameter1,
+ int parameter2,
+ int parameter3WithAVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVerVeryVeryLongNameX)
+{
+ extremeFunction3(
+ parameter1,
+ parameter2,
+ parameter3WithAVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVerVeryVeryLongNameX);
+}
+
+// -------------------------------------------------------------------------------------------------
+// Misc
+// -------------------------------------------------------------------------------------------------
+
+static Q_LOGGING_CATEGORY(log, "qtc.cpptools.builtineditordocumentprocessor", QtWarningMsg)
+
+ int hello; // NOTE: Ops, awkward placement of next token after Q_LOGGING_CATEGORY (semicolon helps)
+
+// -------------------------------------------------------------------------------------------------
+
+int main() {}
+
+#include "clang-format.moc"
diff --git a/tests/fuzzy-test/fuzzytester.cpp b/tests/fuzzy-test/fuzzytester.cpp
index f38ad4736..e601e006f 100644
--- a/tests/fuzzy-test/fuzzytester.cpp
+++ b/tests/fuzzy-test/fuzzytester.cpp
@@ -151,12 +151,7 @@ QStringList FuzzyTester::findAllCommits(const QString &startCommit)
QString allCommitsString;
runGit(QStringList() << "log" << (startCommit + "~1.." + m_headCommit) << "--format=format:%h",
&allCommitsString);
- return allCommitsString.simplified().split(QLatin1Char(' '),
-#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
- QString::SkipEmptyParts);
-#else
- Qt::SkipEmptyParts);
-#endif
+ return allCommitsString.simplified().split(QLatin1Char(' '), Qt::SkipEmptyParts);
}
QString FuzzyTester::findWorkingStartCommit(const QString &startCommit)
diff --git a/tests/lspclient/CMakeLists.txt b/tests/lspclient/CMakeLists.txt
new file mode 100644
index 000000000..ba6f3dbdc
--- /dev/null
+++ b/tests/lspclient/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_qbs_app(qbs_lspclient
+ DEPENDS
+ Qt${QT_VERSION_MAJOR}::Core
+ Qt${QT_VERSION_MAJOR}::Network
+ qbscore
+ qtclsp
+ SOURCES lspclient.cpp
+ )
diff --git a/tests/lspclient/lspclient.cpp b/tests/lspclient/lspclient.cpp
new file mode 100644
index 000000000..40adc9288
--- /dev/null
+++ b/tests/lspclient/lspclient.cpp
@@ -0,0 +1,434 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** 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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <lsp/clientcapabilities.h>
+#include <lsp/completion.h>
+#include <lsp/initializemessages.h>
+#include <lsp/languagefeatures.h>
+#include <lsp/textsynchronization.h>
+
+#include <QBuffer>
+#include <QCommandLineOption>
+#include <QCommandLineParser>
+#include <QCoreApplication>
+#include <QLocalSocket>
+
+#include <cstdlib>
+#include <iostream>
+
+enum class Command {
+ GotoDefinition,
+ Completion,
+};
+
+class LspClient : public QObject
+{
+public:
+ LspClient(Command command, const QString &socketPath, const QString &codeToInsert,
+ int insertLine, int insertColumn,
+ const QString &filePath, int line, int column);
+ void start();
+
+private:
+ void finishWithError(const QString &msg);
+ void exit(int code);
+ void initiateProtocol();
+ void handleIncomingData();
+ void sendMessage(const lsp::JsonObject &msg);
+ void sendMessage(const lsp::JsonRpcMessage &msg);
+ void handleCurrentMessage();
+ void handleInitializeReply();
+ void openDocument();
+ void insertCode();
+ void sendRequest();
+ void handleResponse();
+ void sendGotoDefinitionRequest();
+ void handleGotoDefinitionResponse();
+ void sendCompletionRequest();
+ void handleCompletionResponse();
+ lsp::DocumentUri uri() const;
+ lsp::DocumentUri::PathMapper mapper() const;
+
+ const Command m_command;
+ const QString m_socketPath;
+ const QString m_codeToInsert;
+ const int m_insertLine;
+ const int m_insertColumn;
+ const QString m_filePath;
+ int m_line;
+ int m_column;
+ QLocalSocket m_socket;
+ QBuffer m_incomingData;
+ lsp::BaseMessage m_currentMessage;
+ QJsonObject m_messageObject;
+
+ enum class State { Inactive, Connecting, Initializing, RunningCommand }
+ m_state = State::Inactive;
+};
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ const QCommandLineOption socketOption({"socket", "s"}, "The server socket to connect to.",
+ "socket");
+ const QCommandLineOption gotoDefinitionOption(
+ {"goto-def", "g"}, "Go to definition from the specified location.");
+ const QCommandLineOption completionOption(
+ {"completion", "c"}, "Request completion at the specified location.");
+ const QCommandLineOption insertCodeOption("insert-code",
+ "A piece of code to insert before doing the actual "
+ "operation.",
+ "code");
+ const QCommandLineOption insertLocationOption("insert-location",
+ "The location at which to insert the code.",
+ "<line>:<column>");
+ QCommandLineParser parser;
+ parser.addOptions(
+ {socketOption,
+ insertCodeOption,
+ insertLocationOption,
+ gotoDefinitionOption,
+ completionOption});
+ parser.addHelpOption();
+ parser.addPositionalArgument("location", "The location at which to operate.",
+ "<file>:<line>:<column>");
+ parser.process(app);
+
+ const auto complainAndExit = [&](const char *text) {
+ std::cerr << text << std::endl;
+ parser.showHelp(EXIT_FAILURE);
+ };
+
+ if (!parser.isSet(socketOption))
+ complainAndExit("Socket must be specified.");
+
+ // Initialized to suppress warning. TODO: In C++23, mark lambdas as noreturn instead.
+ Command command = Command::GotoDefinition;
+
+ if (parser.isSet(gotoDefinitionOption))
+ command = Command::GotoDefinition;
+ else if (parser.isSet(completionOption))
+ command = Command::Completion;
+ else
+ complainAndExit("Don't know what to do.");
+
+ if (parser.positionalArguments().size() != 1)
+ complainAndExit("Need location.");
+ const auto complainAboutLocationString = [&] { complainAndExit("Invalid location."); };
+ const QString loc = parser.positionalArguments().first();
+ int sep1 = loc.indexOf(':');
+ if (sep1 <= 0)
+ complainAboutLocationString();
+ if (qbs::Internal::HostOsInfo::isWindowsHost()) {
+ sep1 = loc.indexOf(':', sep1 + 1);
+ if (sep1 < 0)
+ complainAboutLocationString();
+ }
+ const int sep2 = loc.indexOf(':', sep1 + 1);
+ if (sep2 == -1 || sep2 == loc.size() - 1)
+ complainAboutLocationString();
+ const auto extractNumber = [&](const QString &s) {
+ bool ok;
+ const int n = s.toInt(&ok);
+ if (!ok || n <= 0)
+ complainAboutLocationString();
+ return n;
+ };
+ const int line = extractNumber(loc.mid(sep1 + 1, sep2 - sep1 - 1));
+ const int column = extractNumber(loc.mid(sep2 + 1));
+
+ const QString insertLoc = parser.value(insertLocationOption);
+ int insertLine = -1;
+ int insertColumn = -1;
+ if (insertLoc.isEmpty()) {
+ insertLine = line;
+ insertColumn = column;
+ } else {
+ const int sep = insertLoc.indexOf(':');
+ if (sep <= 0)
+ complainAboutLocationString();
+ insertLine = extractNumber(insertLoc.left(sep));
+ insertColumn = extractNumber(insertLoc.mid(sep + 1));
+ }
+
+ LspClient client(command, parser.value(socketOption), parser.value(insertCodeOption),
+ insertLine, insertColumn,
+ QDir::fromNativeSeparators(loc.left(sep1)), line, column);
+ QMetaObject::invokeMethod(&client, &LspClient::start, Qt::QueuedConnection);
+
+ return app.exec();
+}
+
+LspClient::LspClient(Command command, const QString &socketPath, const QString &codeToInsert,
+ int insertLine, int insertColumn,
+ const QString &filePath, int line, int column)
+ : m_command(command), m_socketPath(socketPath), m_codeToInsert(codeToInsert),
+ m_insertLine(insertLine), m_insertColumn(insertColumn),
+ m_filePath(filePath), m_line(line), m_column(column)
+{
+ connect(&m_socket, &QLocalSocket::disconnected, this, [this] {
+ finishWithError("Server disconnected unexpectedly.");
+ });
+ connect(&m_socket, &QLocalSocket::errorOccurred, this, [this] {
+ finishWithError(QString::fromLatin1("Socket error: %1").arg(m_socket.errorString()));
+ });
+ connect(&m_socket, &QLocalSocket::connected, this, &LspClient::initiateProtocol);
+ connect(&m_socket, &QLocalSocket::readyRead, this, &LspClient::handleIncomingData);
+}
+
+void LspClient::start()
+{
+ m_state = State::Connecting;
+ m_incomingData.open(QIODevice::ReadWrite | QIODevice::Append);
+ m_socket.connectToServer(m_socketPath);
+}
+
+void LspClient::finishWithError(const QString &msg)
+{
+ std::cerr << qPrintable(msg) << std::endl;
+ m_socket.disconnectFromServer();
+ exit(EXIT_FAILURE);
+}
+
+void LspClient::exit(int code)
+{
+ m_socket.disconnect(this);
+ qApp->exit(code);
+}
+
+void LspClient::initiateProtocol()
+{
+ if (m_state != State::Connecting) {
+ finishWithError(QString::fromLatin1("State should be %1, was %2.")
+ .arg(int(State::Connecting), int(m_state)));
+ return;
+ }
+ m_state = State::Initializing;
+
+ lsp::DynamicRegistrationCapabilities definitionCaps;
+ definitionCaps.setDynamicRegistration(false);
+ lsp::TextDocumentClientCapabilities docCaps;
+ docCaps.setDefinition(definitionCaps);
+ lsp::ClientCapabilities clientCaps;
+ clientCaps.setTextDocument(docCaps);
+ lsp::InitializeParams initParams;
+ initParams.setCapabilities(clientCaps);
+ sendMessage(lsp::InitializeRequest(initParams));
+}
+
+void LspClient::handleIncomingData()
+{
+ const int pos = m_incomingData.pos();
+ m_incomingData.write(m_socket.readAll());
+ m_incomingData.seek(pos);
+ QString parseError;
+ lsp::BaseMessage::parse(&m_incomingData, parseError, m_currentMessage);
+ if (!parseError.isEmpty()) {
+ return finishWithError(QString::fromLatin1("Error parsing server message: %1.")
+ .arg(parseError));
+ }
+ if (m_currentMessage.isComplete()) {
+ m_incomingData.buffer().remove(0, m_incomingData.pos());
+ m_incomingData.seek(0);
+ handleCurrentMessage();
+ m_currentMessage = {};
+ m_messageObject = {};
+ if (m_socket.state() == QLocalSocket::ConnectedState)
+ handleIncomingData();
+ }
+}
+
+void LspClient::sendMessage(const lsp::JsonObject &msg)
+{
+ sendMessage(lsp::JsonRpcMessage(msg));
+}
+
+void LspClient::sendMessage(const lsp::JsonRpcMessage &msg)
+{
+ lsp::BaseMessage baseMsg = msg.toBaseMessage();
+ m_socket.write(baseMsg.header());
+ m_socket.write(baseMsg.content);
+}
+
+void LspClient::handleCurrentMessage()
+{
+ m_messageObject = lsp::JsonRpcMessage(m_currentMessage).toJsonObject();
+ switch (m_state) {
+ case State::Inactive:
+ case State::Connecting:
+ finishWithError("Received message in non-connected state.");
+ break;
+ case State::Initializing:
+ handleInitializeReply();
+ openDocument();
+ insertCode();
+ sendRequest();
+ break;
+ case State::RunningCommand:
+ handleResponse();
+ break;
+ }
+}
+
+void LspClient::handleInitializeReply()
+{
+ lsp::ServerCapabilities serverCaps = lsp::InitializeRequest::Response(
+ m_messageObject).result().value_or(lsp::InitializeResult()).capabilities();
+ const auto defProvider = serverCaps.definitionProvider();
+ if (!defProvider)
+ return finishWithError("Expected definition provider.");
+ const bool * const defProviderValue = std::get_if<bool>(&(*defProvider));
+ if (!defProviderValue || !*defProviderValue)
+ return finishWithError("Expected definition provider.");
+ sendMessage(lsp::InitializeNotification(lsp::InitializedParams()));
+}
+
+void LspClient::openDocument()
+{
+ QFile file(m_filePath);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ return finishWithError(QString::fromLatin1("Could not open '%1': %2")
+ .arg(m_filePath, file.errorString()));
+ }
+ lsp::TextDocumentItem item;
+ item.setUri(uri());
+ item.setVersion(0);
+ item.setText(QString::fromUtf8(file.readAll()));
+ item.setLanguageId("application/x-qt.qbs+qml");
+ sendMessage(lsp::DidOpenTextDocumentNotification(lsp::DidOpenTextDocumentParams(item)));
+}
+
+void LspClient::insertCode()
+{
+ if (m_codeToInsert.isEmpty())
+ return;
+
+ lsp::VersionedTextDocumentIdentifier docId;
+ docId.setUri(uri());
+ docId.setVersion(1);
+ lsp::DidChangeTextDocumentParams params(docId);
+ lsp::DidChangeTextDocumentParams::TextDocumentContentChangeEvent change;
+ const lsp::Position insertPos(m_insertLine - 1, m_insertColumn- 1);
+ change.setRange({insertPos, insertPos});
+ change.setText(m_codeToInsert);
+ params.setContentChanges({change});
+ sendMessage(lsp::DidChangeTextDocumentNotification(params));
+
+ if (m_insertLine > m_line || (m_insertLine == m_line && m_insertColumn > m_column))
+ return;
+
+ const int newlineCount = m_codeToInsert.count('\n');
+ m_line += newlineCount;
+ m_column += m_codeToInsert.size() - (newlineCount == 0 ? 0 : m_codeToInsert.lastIndexOf('\n'));
+}
+
+void LspClient::sendRequest()
+{
+ m_state = State::RunningCommand;
+ switch (m_command) {
+ case Command::GotoDefinition:
+ return sendGotoDefinitionRequest();
+ case Command::Completion:
+ return sendCompletionRequest();
+ }
+}
+
+void LspClient::handleResponse()
+{
+ const QString error = m_messageObject.value(lsp::errorKey).toObject()
+ .value("message").toString();
+ if (!error.isEmpty())
+ return finishWithError(error);
+
+ switch (m_command) {
+ case Command::GotoDefinition:
+ return handleGotoDefinitionResponse();
+ case Command::Completion:
+ return handleCompletionResponse();
+ }
+}
+
+void LspClient::sendGotoDefinitionRequest()
+{
+ const lsp::TextDocumentIdentifier doc(uri());
+ const lsp::Position pos(m_line - 1, m_column - 1);
+ sendMessage(lsp::GotoDefinitionRequest({doc, pos}));
+}
+
+void LspClient::handleGotoDefinitionResponse()
+{
+ const lsp::GotoResult result(lsp::GotoDefinitionRequest::Response(m_messageObject)
+ .result().value_or(lsp::GotoResult()));
+ QList<lsp::Utils::Link> links;
+ const auto loc2Link = [this](const lsp::Location &loc) { return loc.toLink(mapper()); };
+ if (const auto loc = std::get_if<lsp::Location>(&result)) {
+ links << loc2Link(*loc);
+ } else if (const auto locs = std::get_if<QList<lsp::Location>>(&result)) {
+ links = lsp::Utils::transform(*locs, loc2Link);
+ }
+ for (const lsp::Utils::Link &link : std::as_const(links)) {
+ std::cout << qPrintable(link.targetFilePath) << ':' << link.targetLine << ':'
+ << (link.targetColumn + 1) << std::endl;
+ }
+ exit(EXIT_SUCCESS);
+}
+
+void LspClient::sendCompletionRequest()
+{
+ const lsp::TextDocumentIdentifier doc(uri());
+ const lsp::Position pos(m_line - 1, m_column - 1);
+ sendMessage(lsp::CompletionRequest({doc, pos}));
+}
+
+void LspClient::handleCompletionResponse()
+{
+ const lsp::CompletionResult result(lsp::CompletionRequest::Response(m_messageObject)
+ .result()
+ .value_or(lsp::CompletionResult()));
+ if (const auto items = std::get_if<QList<lsp::CompletionItem>>(&result)) {
+ for (const lsp::CompletionItem &item : *items) {
+ std::cout << qPrintable(item.label());
+ if (item.detail())
+ std::cout << ' ' << qPrintable(*item.detail());
+ std::cout << std::endl;
+ }
+ }
+ exit(EXIT_SUCCESS);
+}
+
+lsp::DocumentUri LspClient::uri() const
+{
+ return lsp::DocumentUri::fromFilePath(lsp::Utils::FilePath::fromUserInput(m_filePath),
+ mapper());
+}
+
+lsp::DocumentUri::PathMapper LspClient::mapper() const
+{
+ return [](const lsp::Utils::FilePath &fp) { return fp; };
+}
diff --git a/tests/lspclient/lspclient.qbs b/tests/lspclient/lspclient.qbs
new file mode 100644
index 000000000..c89b9e0c0
--- /dev/null
+++ b/tests/lspclient/lspclient.qbs
@@ -0,0 +1,10 @@
+QbsApp {
+ name: "qbs_lspclient"
+
+ Depends { name: "qtclsp" }
+ Depends { name: "Qt.network" }
+
+ cpp.defines: base.filter(function(d) { return d !== "QT_NO_CAST_FROM_ASCII"; })
+
+ files: "lspclient.cpp"
+}
diff --git a/tests/tests.qbs b/tests/tests.qbs
index 3cc757c8a..3478ce8aa 100644
--- a/tests/tests.qbs
+++ b/tests/tests.qbs
@@ -4,7 +4,9 @@ Project {
references: [
"auto/auto.qbs",
"benchmarker/benchmarker.qbs",
+ "clang-format-test/clang-format-test.qbs",
"fuzzy-test/fuzzy-test.qbs",
+ "lspclient/lspclient.qbs",
]
AutotestRunner {
diff --git a/tutorial/LICENSE b/tutorial/LICENSE
new file mode 100644
index 000000000..d7cbbf160
--- /dev/null
+++ b/tutorial/LICENSE
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 Ivan Komissarov (abbapoh@gmail.com)
+** Contact: https://www.qt.io/licensing/
+**
+** Files in this directory are part of the Qbs tutorial.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use these files in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this these files under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/ \ No newline at end of file
diff --git a/tutorial/chapter-1/app/app.qbs b/tutorial/chapter-1/app/app.qbs
new file mode 100644
index 000000000..8d0831dc1
--- /dev/null
+++ b/tutorial/chapter-1/app/app.qbs
@@ -0,0 +1,12 @@
+//! [0]
+CppApplication {
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+ version: "1.0.0"
+
+ consoleApplication: true
+ install: true
+ installDebugInformation: true
+}
+//! [0]
diff --git a/tutorial/chapter-1/app/main.c b/tutorial/chapter-1/app/main.c
new file mode 100644
index 000000000..3f3560d25
--- /dev/null
+++ b/tutorial/chapter-1/app/main.c
@@ -0,0 +1,9 @@
+//! [0]
+#include <stdio.h>
+
+int main()
+{
+ printf("Hello, world\n");
+ return 0;
+}
+//! [0]
diff --git a/tutorial/chapter-1/myproject.qbs b/tutorial/chapter-1/myproject.qbs
new file mode 100644
index 000000000..b936f3738
--- /dev/null
+++ b/tutorial/chapter-1/myproject.qbs
@@ -0,0 +1,9 @@
+//! [0]
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs"
+ ]
+}
+//! [0]
diff --git a/tutorial/chapter-2/app/app.qbs b/tutorial/chapter-2/app/app.qbs
new file mode 100644
index 000000000..a3ee757ab
--- /dev/null
+++ b/tutorial/chapter-2/app/app.qbs
@@ -0,0 +1,11 @@
+CppApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+ version: "1.0.0"
+
+ consoleApplication: true
+ install: true
+ installDebugInformation: true
+}
diff --git a/tutorial/chapter-2/app/main.c b/tutorial/chapter-2/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-2/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-2/lib/lib.c b/tutorial/chapter-2/lib/lib.c
new file mode 100644
index 000000000..9fe020ba2
--- /dev/null
+++ b/tutorial/chapter-2/lib/lib.c
@@ -0,0 +1,15 @@
+//! [0]
+
+// lib/lib.cpp
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
+
+//! [0]
diff --git a/tutorial/chapter-2/lib/lib.h b/tutorial/chapter-2/lib/lib.h
new file mode 100644
index 000000000..efc56ae0b
--- /dev/null
+++ b/tutorial/chapter-2/lib/lib.h
@@ -0,0 +1,11 @@
+//! [0]
+
+// lib/lib.h
+#ifndef LIB_H
+#define LIB_H
+
+const char *get_string();
+
+#endif // LIB_H
+
+//! [0]
diff --git a/tutorial/chapter-2/lib/lib.qbs b/tutorial/chapter-2/lib/lib.qbs
new file mode 100644
index 000000000..378d855a6
--- /dev/null
+++ b/tutorial/chapter-2/lib/lib.qbs
@@ -0,0 +1,28 @@
+//! [0]
+StaticLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ ]
+ version: "1.0.0"
+ install: true
+
+ //! [1]
+ Depends { name: 'cpp' }
+ cpp.defines: ['CRUCIAL_DEFINE']
+ //! [1]
+
+ //! [2]
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+ //! [2]
+
+ //! [3]
+ Depends { name: 'bundle' }
+ bundle.isBundle: false
+ //! [3]
+}
+//! [0]
diff --git a/tutorial/chapter-2/myproject.qbs b/tutorial/chapter-2/myproject.qbs
new file mode 100644
index 000000000..17ef04ac9
--- /dev/null
+++ b/tutorial/chapter-2/myproject.qbs
@@ -0,0 +1,10 @@
+//! [0]
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs"
+ ]
+}
+//! [0]
diff --git a/tutorial/chapter-3/app/app.qbs b/tutorial/chapter-3/app/app.qbs
new file mode 100644
index 000000000..bb7108581
--- /dev/null
+++ b/tutorial/chapter-3/app/app.qbs
@@ -0,0 +1,29 @@
+//! [1]
+// app/app.qbs
+import qbs.FileInfo
+
+CppApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+ version: "1.0.0"
+
+ consoleApplication: true
+ install: true
+
+ //! [0]
+ cpp.rpaths: {
+ if (!cpp.rpathOrigin)
+ return [];
+ return [
+ FileInfo.joinPaths(
+ cpp.rpathOrigin,
+ FileInfo.relativePath(
+ FileInfo.joinPaths("/", product.installDir),
+ FileInfo.joinPaths("/", "lib")))
+ ];
+ }
+ //! [0]
+}
+//! [1]
diff --git a/tutorial/chapter-3/app/main.c b/tutorial/chapter-3/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-3/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-3/lib/lib.c b/tutorial/chapter-3/lib/lib.c
new file mode 100644
index 000000000..1cbc5ef21
--- /dev/null
+++ b/tutorial/chapter-3/lib/lib.c
@@ -0,0 +1,16 @@
+
+//! [0]
+
+// lib/lib.cpp
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
+
+//! [0]
diff --git a/tutorial/chapter-3/lib/lib.h b/tutorial/chapter-3/lib/lib.h
new file mode 100644
index 000000000..772e1d82b
--- /dev/null
+++ b/tutorial/chapter-3/lib/lib.h
@@ -0,0 +1,11 @@
+#ifndef LIB_H
+#define LIB_H
+
+//! [0]
+// lib/lib.h
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+//! [0]
+
+#endif // LIB_H
diff --git a/tutorial/chapter-3/lib/lib.qbs b/tutorial/chapter-3/lib/lib.qbs
new file mode 100644
index 000000000..a52c29d97
--- /dev/null
+++ b/tutorial/chapter-3/lib/lib.qbs
@@ -0,0 +1,26 @@
+//! [0]
+// lib/lib.qbs
+
+DynamicLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ "lib_global.h",
+ ]
+ version: "1.0.0"
+ install: true
+
+ Depends { name: "cpp" }
+ cpp.defines: ["MYLIB_LIBRARY", "CRUCIAL_DEFINE"]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+
+ Depends { name: "bundle" }
+ bundle.isBundle: false
+}
+//! [0]
diff --git a/tutorial/chapter-3/lib/lib_global.h b/tutorial/chapter-3/lib/lib_global.h
new file mode 100644
index 000000000..07b7978ef
--- /dev/null
+++ b/tutorial/chapter-3/lib/lib_global.h
@@ -0,0 +1,23 @@
+//! [0]
+// lib/lib_global.h
+
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MYLIB_DECL_EXPORT __declspec(dllexport)
+#define MYLIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MYLIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MYLIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MYLIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MYLIB_DECL_IMPORT
+#endif
+
+#endif // LIB_GLOBAL_H
+
+//! [0]
diff --git a/tutorial/chapter-3/myproject.qbs b/tutorial/chapter-3/myproject.qbs
new file mode 100644
index 000000000..152d6cce5
--- /dev/null
+++ b/tutorial/chapter-3/myproject.qbs
@@ -0,0 +1,8 @@
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs"
+ ]
+}
diff --git a/tutorial/chapter-4/app/app.qbs b/tutorial/chapter-4/app/app.qbs
new file mode 100644
index 000000000..7051ff554
--- /dev/null
+++ b/tutorial/chapter-4/app/app.qbs
@@ -0,0 +1,10 @@
+//! [0]
+// app/app.qbs
+
+MyApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+}
+//! [0]
diff --git a/tutorial/chapter-4/app/main.c b/tutorial/chapter-4/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-4/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-4/lib/lib.c b/tutorial/chapter-4/lib/lib.c
new file mode 100644
index 000000000..5dcae0de3
--- /dev/null
+++ b/tutorial/chapter-4/lib/lib.c
@@ -0,0 +1,10 @@
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
diff --git a/tutorial/chapter-4/lib/lib.h b/tutorial/chapter-4/lib/lib.h
new file mode 100644
index 000000000..ef39ca4a1
--- /dev/null
+++ b/tutorial/chapter-4/lib/lib.h
@@ -0,0 +1,8 @@
+#ifndef LIB_H
+#define LIB_H
+
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+
+#endif // LIB_H
diff --git a/tutorial/chapter-4/lib/lib.qbs b/tutorial/chapter-4/lib/lib.qbs
new file mode 100644
index 000000000..1f7bf6e6b
--- /dev/null
+++ b/tutorial/chapter-4/lib/lib.qbs
@@ -0,0 +1,13 @@
+//! [0]
+// lib/lib.qbs
+
+MyLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ "lib_global.h",
+ ]
+ cpp.defines: base.concat(["CRUCIAL_DEFINE"])
+}
+//! [0]
diff --git a/tutorial/chapter-4/lib/lib_global.h b/tutorial/chapter-4/lib/lib_global.h
new file mode 100644
index 000000000..76cce8d36
--- /dev/null
+++ b/tutorial/chapter-4/lib/lib_global.h
@@ -0,0 +1,18 @@
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MYLIB_DECL_EXPORT __declspec(dllexport)
+#define MYLIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MYLIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MYLIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MYLIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MYLIB_DECL_IMPORT
+#endif
+
+#endif // LIB_GLOBAL_H
diff --git a/tutorial/chapter-4/myproject.qbs b/tutorial/chapter-4/myproject.qbs
new file mode 100644
index 000000000..d429542e7
--- /dev/null
+++ b/tutorial/chapter-4/myproject.qbs
@@ -0,0 +1,11 @@
+//! [0]
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs"
+ ]
+ qbsSearchPaths: "qbs"
+}
+//! [0]
diff --git a/tutorial/chapter-4/qbs/imports/MyApplication.qbs b/tutorial/chapter-4/qbs/imports/MyApplication.qbs
new file mode 100644
index 000000000..e98794f3c
--- /dev/null
+++ b/tutorial/chapter-4/qbs/imports/MyApplication.qbs
@@ -0,0 +1,23 @@
+//! [0]
+// qbs/imports/MyApplication.qbs
+
+import qbs.FileInfo
+
+CppApplication {
+ version: "1.0.0"
+ consoleApplication: true
+ install: true
+
+ cpp.rpaths: {
+ if (!cpp.rpathOrigin)
+ return [];
+ return [
+ FileInfo.joinPaths(
+ cpp.rpathOrigin,
+ FileInfo.relativePath(
+ FileInfo.joinPaths("/", product.installDir),
+ FileInfo.joinPaths("/", "lib")))
+ ];
+ }
+}
+//! [0]
diff --git a/tutorial/chapter-4/qbs/imports/MyLibrary.qbs b/tutorial/chapter-4/qbs/imports/MyLibrary.qbs
new file mode 100644
index 000000000..c819fa38e
--- /dev/null
+++ b/tutorial/chapter-4/qbs/imports/MyLibrary.qbs
@@ -0,0 +1,21 @@
+//! [0]
+// qbs/imports/MyLibrary.qbs
+
+DynamicLibrary {
+ version: "1.0.0"
+ install: true
+
+ Depends { name: 'cpp' }
+ property string libraryMacro: name.replace(" ", "_").toUpperCase() + "_LIBRARY"
+ cpp.defines: [libraryMacro]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+
+ Depends { name: 'bundle' }
+ bundle.isBundle: false
+}
+//! [0]
diff --git a/tutorial/chapter-5/app/app.qbs b/tutorial/chapter-5/app/app.qbs
new file mode 100644
index 000000000..84c2430e4
--- /dev/null
+++ b/tutorial/chapter-5/app/app.qbs
@@ -0,0 +1,6 @@
+MyApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+}
diff --git a/tutorial/chapter-5/app/main.c b/tutorial/chapter-5/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-5/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-5/lib/lib.c b/tutorial/chapter-5/lib/lib.c
new file mode 100644
index 000000000..5dcae0de3
--- /dev/null
+++ b/tutorial/chapter-5/lib/lib.c
@@ -0,0 +1,10 @@
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
diff --git a/tutorial/chapter-5/lib/lib.h b/tutorial/chapter-5/lib/lib.h
new file mode 100644
index 000000000..ef39ca4a1
--- /dev/null
+++ b/tutorial/chapter-5/lib/lib.h
@@ -0,0 +1,8 @@
+#ifndef LIB_H
+#define LIB_H
+
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+
+#endif // LIB_H
diff --git a/tutorial/chapter-5/lib/lib.qbs b/tutorial/chapter-5/lib/lib.qbs
new file mode 100644
index 000000000..7c303c8aa
--- /dev/null
+++ b/tutorial/chapter-5/lib/lib.qbs
@@ -0,0 +1,9 @@
+MyLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ "lib_global.h",
+ ]
+ cpp.defines: base.concat(['CRUCIAL_DEFINE'])
+}
diff --git a/tutorial/chapter-5/lib/lib_global.h b/tutorial/chapter-5/lib/lib_global.h
new file mode 100644
index 000000000..76cce8d36
--- /dev/null
+++ b/tutorial/chapter-5/lib/lib_global.h
@@ -0,0 +1,18 @@
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MYLIB_DECL_EXPORT __declspec(dllexport)
+#define MYLIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MYLIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MYLIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MYLIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MYLIB_DECL_IMPORT
+#endif
+
+#endif // LIB_GLOBAL_H
diff --git a/tutorial/chapter-5/myproject.qbs b/tutorial/chapter-5/myproject.qbs
new file mode 100644
index 000000000..66e32b220
--- /dev/null
+++ b/tutorial/chapter-5/myproject.qbs
@@ -0,0 +1,15 @@
+Project {
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ // ![0]
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs",
+ "test/test.qbs",
+ ]
+ // ![0]
+ qbsSearchPaths: "qbs"
+ AutotestRunner {
+ timeout: 60
+ }
+}
diff --git a/tutorial/chapter-5/qbs/imports/MyApplication.qbs b/tutorial/chapter-5/qbs/imports/MyApplication.qbs
new file mode 100644
index 000000000..d8c4de25a
--- /dev/null
+++ b/tutorial/chapter-5/qbs/imports/MyApplication.qbs
@@ -0,0 +1,20 @@
+import qbs.FileInfo
+
+CppApplication {
+ version: "1.0.0"
+ consoleApplication: true
+ install: true
+ installDebugInformation: true
+
+ cpp.rpaths: {
+ if (!cpp.rpathOrigin)
+ return [];
+ return [
+ FileInfo.joinPaths(
+ cpp.rpathOrigin,
+ FileInfo.relativePath(
+ FileInfo.joinPaths("/", product.installDir),
+ FileInfo.joinPaths("/", "lib")))
+ ];
+ }
+}
diff --git a/tutorial/chapter-5/qbs/imports/MyAutoTest.qbs b/tutorial/chapter-5/qbs/imports/MyAutoTest.qbs
new file mode 100644
index 000000000..bb6712e85
--- /dev/null
+++ b/tutorial/chapter-5/qbs/imports/MyAutoTest.qbs
@@ -0,0 +1,7 @@
+//! [0]
+// qbs/imports/MyAutoTest.qbs
+
+MyApplication {
+ type: base.concat(["autotest"])
+}
+//! [0]
diff --git a/tutorial/chapter-5/qbs/imports/MyLibrary.qbs b/tutorial/chapter-5/qbs/imports/MyLibrary.qbs
new file mode 100644
index 000000000..5e6c2687a
--- /dev/null
+++ b/tutorial/chapter-5/qbs/imports/MyLibrary.qbs
@@ -0,0 +1,17 @@
+DynamicLibrary {
+ version: "1.0.0"
+ install: true
+
+ Depends { name: 'cpp' }
+ property string libraryMacro: name.replace(" ", "_").toUpperCase() + "_LIBRARY"
+ cpp.defines: [libraryMacro]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+
+ Depends { name: 'bundle' }
+ bundle.isBundle: false
+}
diff --git a/tutorial/chapter-5/test/test.c b/tutorial/chapter-5/test/test.c
new file mode 100644
index 000000000..f3bdf646c
--- /dev/null
+++ b/tutorial/chapter-5/test/test.c
@@ -0,0 +1,22 @@
+//! [0]
+// test/test.c
+
+#include "lib.h"
+
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+ if (argc > 2) {
+ printf("usage: test [value]\n");
+ return 1;
+ }
+ const char *expected = argc == 2 ? argv[1] : "Hello from library";
+ if (strcmp(get_string(), expected) != 0) {
+ printf("text differs\n");
+ return 1;
+ }
+ return 0;
+}
+//! [0] \ No newline at end of file
diff --git a/tutorial/chapter-5/test/test.qbs b/tutorial/chapter-5/test/test.qbs
new file mode 100644
index 000000000..1eab70c55
--- /dev/null
+++ b/tutorial/chapter-5/test/test.qbs
@@ -0,0 +1,9 @@
+//! [0]
+// test/test.qbs
+
+MyAutoTest {
+ Depends { name: "mylib" }
+ name: "mytest"
+ files: "test.c"
+}
+//! [0]
diff --git a/tutorial/chapter-6/app/app.qbs b/tutorial/chapter-6/app/app.qbs
new file mode 100644
index 000000000..84c2430e4
--- /dev/null
+++ b/tutorial/chapter-6/app/app.qbs
@@ -0,0 +1,6 @@
+MyApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+}
diff --git a/tutorial/chapter-6/app/main.c b/tutorial/chapter-6/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-6/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-6/lib/lib.c b/tutorial/chapter-6/lib/lib.c
new file mode 100644
index 000000000..5dcae0de3
--- /dev/null
+++ b/tutorial/chapter-6/lib/lib.c
@@ -0,0 +1,10 @@
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
diff --git a/tutorial/chapter-6/lib/lib.h b/tutorial/chapter-6/lib/lib.h
new file mode 100644
index 000000000..ef39ca4a1
--- /dev/null
+++ b/tutorial/chapter-6/lib/lib.h
@@ -0,0 +1,8 @@
+#ifndef LIB_H
+#define LIB_H
+
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+
+#endif // LIB_H
diff --git a/tutorial/chapter-6/lib/lib.qbs b/tutorial/chapter-6/lib/lib.qbs
new file mode 100644
index 000000000..7c303c8aa
--- /dev/null
+++ b/tutorial/chapter-6/lib/lib.qbs
@@ -0,0 +1,9 @@
+MyLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ "lib_global.h",
+ ]
+ cpp.defines: base.concat(['CRUCIAL_DEFINE'])
+}
diff --git a/tutorial/chapter-6/lib/lib_global.h b/tutorial/chapter-6/lib/lib_global.h
new file mode 100644
index 000000000..76cce8d36
--- /dev/null
+++ b/tutorial/chapter-6/lib/lib_global.h
@@ -0,0 +1,18 @@
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MYLIB_DECL_EXPORT __declspec(dllexport)
+#define MYLIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MYLIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MYLIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MYLIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MYLIB_DECL_IMPORT
+#endif
+
+#endif // LIB_GLOBAL_H
diff --git a/tutorial/chapter-6/myproject.qbs b/tutorial/chapter-6/myproject.qbs
new file mode 100644
index 000000000..86537af23
--- /dev/null
+++ b/tutorial/chapter-6/myproject.qbs
@@ -0,0 +1,35 @@
+//! [0]
+Project {
+ property string version: "1.0.0"
+ property bool installDebugInformation: true
+ property bool withTests: false
+ property stringList autotestArguments: []
+ property stringList autotestWrapper: []
+
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ // ...
+ //! [0]
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs",
+ ]
+ qbsSearchPaths: "qbs"
+
+ //! [1]
+ SubProject {
+ filePath: "test/test.qbs"
+ Properties {
+ condition: parent.withTests
+ }
+ }
+ //! [1]
+
+ //! [2]
+ AutotestRunner {
+ condition: parent.withTests
+ arguments: parent.autotestArguments
+ wrapper: parent.autotestWrapper
+ }
+ //! [2]
+}
diff --git a/tutorial/chapter-6/qbs/imports/MyApplication.qbs b/tutorial/chapter-6/qbs/imports/MyApplication.qbs
new file mode 100644
index 000000000..875bc10f3
--- /dev/null
+++ b/tutorial/chapter-6/qbs/imports/MyApplication.qbs
@@ -0,0 +1,23 @@
+import qbs.FileInfo
+
+//! [0]
+CppApplication {
+ version: project.version
+ consoleApplication: true
+ install: true
+ installDebugInformation: project.installDebugInformation
+ // ...
+ //! [0]
+
+ cpp.rpaths: {
+ if (!cpp.rpathOrigin)
+ return [];
+ return [
+ FileInfo.joinPaths(
+ cpp.rpathOrigin,
+ FileInfo.relativePath(
+ FileInfo.joinPaths("/", product.installDir),
+ FileInfo.joinPaths("/", "lib")))
+ ];
+ }
+}
diff --git a/tutorial/chapter-6/qbs/imports/MyAutoTest.qbs b/tutorial/chapter-6/qbs/imports/MyAutoTest.qbs
new file mode 100644
index 000000000..ba420e4d2
--- /dev/null
+++ b/tutorial/chapter-6/qbs/imports/MyAutoTest.qbs
@@ -0,0 +1,3 @@
+MyApplication {
+ type: base.concat(["autotest"])
+}
diff --git a/tutorial/chapter-6/qbs/imports/MyLibrary.qbs b/tutorial/chapter-6/qbs/imports/MyLibrary.qbs
new file mode 100644
index 000000000..ef27bf57b
--- /dev/null
+++ b/tutorial/chapter-6/qbs/imports/MyLibrary.qbs
@@ -0,0 +1,18 @@
+DynamicLibrary {
+ version: project.version
+ install: true
+ installDebugInformation: project.installDebugInformation
+
+ Depends { name: 'cpp' }
+ property string libraryMacro: name.replace(" ", "_").toUpperCase() + "_LIBRARY"
+ cpp.defines: [libraryMacro]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+
+ Depends { name: 'bundle' }
+ bundle.isBundle: false
+}
diff --git a/tutorial/chapter-6/test/test.c b/tutorial/chapter-6/test/test.c
new file mode 100644
index 000000000..1fef24685
--- /dev/null
+++ b/tutorial/chapter-6/test/test.c
@@ -0,0 +1,18 @@
+#include "lib.h"
+
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+ if (argc > 2) {
+ printf("usage: test [value]\n");
+ return 1;
+ }
+ const char *expected = argc == 2 ? argv[1] : "Hello from library";
+ if (strcmp(get_string(), expected) != 0) {
+ printf("text differs\n");
+ return 1;
+ }
+ return 0;
+}
diff --git a/tutorial/chapter-6/test/test.qbs b/tutorial/chapter-6/test/test.qbs
new file mode 100644
index 000000000..1e911da47
--- /dev/null
+++ b/tutorial/chapter-6/test/test.qbs
@@ -0,0 +1,5 @@
+MyAutoTest {
+ Depends { name: "mylib" }
+ name: "mytest"
+ files: "test.c"
+}
diff --git a/tutorial/chapter-7/app/app.qbs b/tutorial/chapter-7/app/app.qbs
new file mode 100644
index 000000000..84c2430e4
--- /dev/null
+++ b/tutorial/chapter-7/app/app.qbs
@@ -0,0 +1,6 @@
+MyApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+}
diff --git a/tutorial/chapter-7/app/main.c b/tutorial/chapter-7/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-7/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-7/lib/lib.c b/tutorial/chapter-7/lib/lib.c
new file mode 100644
index 000000000..5dcae0de3
--- /dev/null
+++ b/tutorial/chapter-7/lib/lib.c
@@ -0,0 +1,10 @@
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
diff --git a/tutorial/chapter-7/lib/lib.h b/tutorial/chapter-7/lib/lib.h
new file mode 100644
index 000000000..ef39ca4a1
--- /dev/null
+++ b/tutorial/chapter-7/lib/lib.h
@@ -0,0 +1,8 @@
+#ifndef LIB_H
+#define LIB_H
+
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+
+#endif // LIB_H
diff --git a/tutorial/chapter-7/lib/lib.qbs b/tutorial/chapter-7/lib/lib.qbs
new file mode 100644
index 000000000..eccf6844d
--- /dev/null
+++ b/tutorial/chapter-7/lib/lib.qbs
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of Qbs.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance 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.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+MyLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ ]
+ Depends { name: 'cpp' }
+ cpp.defines: ['CRUCIAL_DEFINE']
+}
+
diff --git a/tutorial/chapter-7/lib/lib_global.h b/tutorial/chapter-7/lib/lib_global.h
new file mode 100644
index 000000000..76cce8d36
--- /dev/null
+++ b/tutorial/chapter-7/lib/lib_global.h
@@ -0,0 +1,18 @@
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MYLIB_DECL_EXPORT __declspec(dllexport)
+#define MYLIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MYLIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MYLIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MYLIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MYLIB_DECL_IMPORT
+#endif
+
+#endif // LIB_GLOBAL_H
diff --git a/tutorial/chapter-7/myproject.qbs b/tutorial/chapter-7/myproject.qbs
new file mode 100644
index 000000000..aa7ec90bd
--- /dev/null
+++ b/tutorial/chapter-7/myproject.qbs
@@ -0,0 +1,27 @@
+Project {
+ property bool withTests: true
+ property bool installDebugInformation: true
+ property stringList autotestArguments: []
+ property stringList autotestWrapper: []
+
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs",
+ ]
+ qbsSearchPaths: "qbs"
+
+ SubProject {
+ filePath: "test/test.qbs"
+ Properties {
+ condition: parent.withTests
+ }
+ }
+
+ AutotestRunner {
+ condition: parent.withTests
+ arguments: parent.autotestArguments
+ wrapper: parent.autotestWrapper
+ }
+}
diff --git a/tutorial/chapter-7/qbs/imports/MyApplication.qbs b/tutorial/chapter-7/qbs/imports/MyApplication.qbs
new file mode 100644
index 000000000..622c3f4be
--- /dev/null
+++ b/tutorial/chapter-7/qbs/imports/MyApplication.qbs
@@ -0,0 +1,13 @@
+//! [0]
+CppApplication {
+ Depends { name: "mybuildconfig" }
+ installDir: mybuildconfig.appInstallDir
+
+ version: "1.0.0"
+ // ...
+//! [0]
+
+ consoleApplication: true
+ install: true
+ installDebugInformation: project.installDebugInformation
+}
diff --git a/tutorial/chapter-7/qbs/imports/MyAutoTest.qbs b/tutorial/chapter-7/qbs/imports/MyAutoTest.qbs
new file mode 100644
index 000000000..1faecdc33
--- /dev/null
+++ b/tutorial/chapter-7/qbs/imports/MyAutoTest.qbs
@@ -0,0 +1,3 @@
+MyApplication {
+ type: ["application", "autotest"]
+}
diff --git a/tutorial/chapter-7/qbs/imports/MyLibrary.qbs b/tutorial/chapter-7/qbs/imports/MyLibrary.qbs
new file mode 100644
index 000000000..7c130ba17
--- /dev/null
+++ b/tutorial/chapter-7/qbs/imports/MyLibrary.qbs
@@ -0,0 +1,25 @@
+DynamicLibrary {
+ version: project.version
+ install: true
+ installDebugInformation: project.installDebugInformation
+//! [0]
+// qbs/imports/MyLibrary.qbs
+ // ...
+ Depends { name: "mybuildconfig" }
+ installDir: mybuildconfig.libInstallDir
+
+ Depends { name: "cpp" }
+ property string libraryMacro: name.replace(" ", "_").toUpperCase() + "_LIBRARY"
+ cpp.defines: [libraryMacro]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ // ...
+//! [0]
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ }
+
+ Depends { name: 'bundle' }
+ bundle.isBundle: false
+}
diff --git a/tutorial/chapter-7/qbs/modules/mybuildconfig/mybuildconfig.qbs b/tutorial/chapter-7/qbs/modules/mybuildconfig/mybuildconfig.qbs
new file mode 100644
index 000000000..73234bb0f
--- /dev/null
+++ b/tutorial/chapter-7/qbs/modules/mybuildconfig/mybuildconfig.qbs
@@ -0,0 +1,26 @@
+import qbs.FileInfo
+
+//! [1]
+//! [0]
+// qbs/modules/mybuildconfig.qbs
+Module {
+ property string appInstallDir: "bin"
+ property string libDirName: "lib"
+ property string libInstallDir: qbs.targetOS.contains("windows") ? appInstallDir : libDirName
+ //! [0]
+
+ Depends { name: "cpp" }
+
+ property bool enableRPath: true
+ property stringList libRPaths: {
+ if (enableRPath && cpp.rpathOrigin && product.installDir) {
+ return [FileInfo.joinPaths(cpp.rpathOrigin, FileInfo.relativePath(
+ FileInfo.joinPaths('/', product.installDir),
+ FileInfo.joinPaths('/', libInstallDir)))];
+ }
+ return [];
+ }
+
+ cpp.rpaths: libRPaths
+}
+//! [1]
diff --git a/tutorial/chapter-7/test/test.c b/tutorial/chapter-7/test/test.c
new file mode 100644
index 000000000..1fef24685
--- /dev/null
+++ b/tutorial/chapter-7/test/test.c
@@ -0,0 +1,18 @@
+#include "lib.h"
+
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+ if (argc > 2) {
+ printf("usage: test [value]\n");
+ return 1;
+ }
+ const char *expected = argc == 2 ? argv[1] : "Hello from library";
+ if (strcmp(get_string(), expected) != 0) {
+ printf("text differs\n");
+ return 1;
+ }
+ return 0;
+}
diff --git a/tutorial/chapter-7/test/test.qbs b/tutorial/chapter-7/test/test.qbs
new file mode 100644
index 000000000..1e911da47
--- /dev/null
+++ b/tutorial/chapter-7/test/test.qbs
@@ -0,0 +1,5 @@
+MyAutoTest {
+ Depends { name: "mylib" }
+ name: "mytest"
+ files: "test.c"
+}
diff --git a/tutorial/chapter-8/app/app.qbs b/tutorial/chapter-8/app/app.qbs
new file mode 100644
index 000000000..84c2430e4
--- /dev/null
+++ b/tutorial/chapter-8/app/app.qbs
@@ -0,0 +1,6 @@
+MyApplication {
+ Depends { name: "mylib" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+}
diff --git a/tutorial/chapter-8/app/main.c b/tutorial/chapter-8/app/main.c
new file mode 100644
index 000000000..a560d1ba1
--- /dev/null
+++ b/tutorial/chapter-8/app/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+#include "lib.h"
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ return 0;
+}
diff --git a/tutorial/chapter-8/lib/lib.c b/tutorial/chapter-8/lib/lib.c
new file mode 100644
index 000000000..5dcae0de3
--- /dev/null
+++ b/tutorial/chapter-8/lib/lib.c
@@ -0,0 +1,10 @@
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
diff --git a/tutorial/chapter-8/lib/lib.h b/tutorial/chapter-8/lib/lib.h
new file mode 100644
index 000000000..ef39ca4a1
--- /dev/null
+++ b/tutorial/chapter-8/lib/lib.h
@@ -0,0 +1,8 @@
+#ifndef LIB_H
+#define LIB_H
+
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+
+#endif // LIB_H
diff --git a/tutorial/chapter-8/lib/lib.qbs b/tutorial/chapter-8/lib/lib.qbs
new file mode 100644
index 000000000..e8cf9bdb9
--- /dev/null
+++ b/tutorial/chapter-8/lib/lib.qbs
@@ -0,0 +1,10 @@
+MyLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ ]
+ Depends { name: 'cpp' }
+ cpp.defines: ['CRUCIAL_DEFINE']
+}
+
diff --git a/tutorial/chapter-8/lib/lib_global.h b/tutorial/chapter-8/lib/lib_global.h
new file mode 100644
index 000000000..d830dd8ab
--- /dev/null
+++ b/tutorial/chapter-8/lib/lib_global.h
@@ -0,0 +1,25 @@
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MY_LIB_DECL_EXPORT __declspec(dllexport)
+#define MY_LIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MY_LIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MY_LIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+// ![0]
+// lib/lib_global.h
+#if defined(MYLIB_STATIC_LIBRARY)
+#define MYLIB_EXPORT
+#else
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MY_LIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MY_LIB_DECL_IMPORT
+#endif
+#endif
+// ![0]
+
+#endif // LIB_GLOBAL_H \ No newline at end of file
diff --git a/tutorial/chapter-8/myproject.qbs b/tutorial/chapter-8/myproject.qbs
new file mode 100644
index 000000000..aa7ec90bd
--- /dev/null
+++ b/tutorial/chapter-8/myproject.qbs
@@ -0,0 +1,27 @@
+Project {
+ property bool withTests: true
+ property bool installDebugInformation: true
+ property stringList autotestArguments: []
+ property stringList autotestWrapper: []
+
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs",
+ ]
+ qbsSearchPaths: "qbs"
+
+ SubProject {
+ filePath: "test/test.qbs"
+ Properties {
+ condition: parent.withTests
+ }
+ }
+
+ AutotestRunner {
+ condition: parent.withTests
+ arguments: parent.autotestArguments
+ wrapper: parent.autotestWrapper
+ }
+}
diff --git a/tutorial/chapter-8/qbs/imports/MyApplication.qbs b/tutorial/chapter-8/qbs/imports/MyApplication.qbs
new file mode 100644
index 000000000..11b3bfb54
--- /dev/null
+++ b/tutorial/chapter-8/qbs/imports/MyApplication.qbs
@@ -0,0 +1,10 @@
+CppApplication {
+ Depends { name: "mybuildconfig" }
+ version: "1.0.0"
+
+ cpp.rpaths: mybuildconfig.libRPaths
+ consoleApplication: true
+ installDir: mybuildconfig.appInstallDir
+ install: true
+ installDebugInformation: project.installDebugInformation
+}
diff --git a/tutorial/chapter-8/qbs/imports/MyAutoTest.qbs b/tutorial/chapter-8/qbs/imports/MyAutoTest.qbs
new file mode 100644
index 000000000..1faecdc33
--- /dev/null
+++ b/tutorial/chapter-8/qbs/imports/MyAutoTest.qbs
@@ -0,0 +1,3 @@
+MyApplication {
+ type: ["application", "autotest"]
+}
diff --git a/tutorial/chapter-8/qbs/imports/MyLibrary.qbs b/tutorial/chapter-8/qbs/imports/MyLibrary.qbs
new file mode 100644
index 000000000..947706dd4
--- /dev/null
+++ b/tutorial/chapter-8/qbs/imports/MyLibrary.qbs
@@ -0,0 +1,26 @@
+// ![0]
+Library {
+ Depends { name: "cpp" }
+ Depends { name: "mybuildconfig" }
+ type: mybuildconfig.staticBuild ? "staticlibrary" : "dynamiclibrary"
+ version: "1.0.0"
+ install: !mybuildconfig.staticBuild || mybuildconfig.installStaticLib
+ installDir: mybuildconfig.libInstallDir
+
+ readonly property string _nameUpper : name.replace(" ", "_").toUpperCase()
+ property string libraryMacro: _nameUpper + "_LIBRARY"
+ property string staticLibraryMacro: _nameUpper + "_STATIC_LIBRARY"
+ cpp.defines: mybuildconfig.staticBuild ? [staticLibraryMacro] : [libraryMacro]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ cpp.defines: exportingProduct.mybuildconfig.staticBuild
+ ? [exportingProduct.staticLibraryMacro] : []
+ }
+
+ Depends { name: "bundle" }
+ bundle.isBundle: false
+}
+// ![0]
diff --git a/tutorial/chapter-8/qbs/modules/mybuildconfig/mybuildconfig.qbs b/tutorial/chapter-8/qbs/modules/mybuildconfig/mybuildconfig.qbs
new file mode 100644
index 000000000..0f13dde4d
--- /dev/null
+++ b/tutorial/chapter-8/qbs/modules/mybuildconfig/mybuildconfig.qbs
@@ -0,0 +1,25 @@
+import qbs.FileInfo
+
+Module {
+ Depends { name: "cpp" }
+
+ property string appInstallDir: "bin"
+ property string libDirName: "lib"
+ property string libInstallDir: qbs.targetOS.contains("windows") ? "bin" : libDirName
+ property bool staticBuild: false
+ property bool installStaticLib: true
+ property bool enableRPath: true
+
+ property stringList libRPaths: {
+ if (enableRPath && cpp.rpathOrigin && product.installDir) {
+ return [
+ FileInfo.joinPaths(
+ cpp.rpathOrigin,
+ FileInfo.relativePath(
+ FileInfo.joinPaths('/', product.installDir),
+ FileInfo.joinPaths('/', libDirName)))
+ ];
+ }
+ return [];
+ }
+}
diff --git a/tutorial/chapter-8/test/test.c b/tutorial/chapter-8/test/test.c
new file mode 100644
index 000000000..1fef24685
--- /dev/null
+++ b/tutorial/chapter-8/test/test.c
@@ -0,0 +1,18 @@
+#include "lib.h"
+
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+ if (argc > 2) {
+ printf("usage: test [value]\n");
+ return 1;
+ }
+ const char *expected = argc == 2 ? argv[1] : "Hello from library";
+ if (strcmp(get_string(), expected) != 0) {
+ printf("text differs\n");
+ return 1;
+ }
+ return 0;
+}
diff --git a/tutorial/chapter-8/test/test.qbs b/tutorial/chapter-8/test/test.qbs
new file mode 100644
index 000000000..1e911da47
--- /dev/null
+++ b/tutorial/chapter-8/test/test.qbs
@@ -0,0 +1,5 @@
+MyAutoTest {
+ Depends { name: "mylib" }
+ name: "mytest"
+ files: "test.c"
+}
diff --git a/tutorial/chapter-9/app/app.qbs b/tutorial/chapter-9/app/app.qbs
new file mode 100644
index 000000000..837b66da3
--- /dev/null
+++ b/tutorial/chapter-9/app/app.qbs
@@ -0,0 +1,9 @@
+//! [0]
+MyApplication {
+ Depends { name: "mylib" }
+ Depends { name: "version_header" }
+ name: "My Application"
+ targetName: "myapp"
+ files: "main.c"
+}
+//! [0]
diff --git a/tutorial/chapter-9/app/main.c b/tutorial/chapter-9/app/main.c
new file mode 100644
index 000000000..d6cefc2ff
--- /dev/null
+++ b/tutorial/chapter-9/app/main.c
@@ -0,0 +1,14 @@
+//![0]
+#include <lib.h>
+#include <version.h>
+
+#include <stdio.h>
+
+int main()
+{
+ printf("Hello, world\n");
+ printf("%s\n", get_string());
+ printf("ProductVersion = %s\n", kProductVersion);
+ return 0;
+}
+//![0]
diff --git a/tutorial/chapter-9/lib/lib.c b/tutorial/chapter-9/lib/lib.c
new file mode 100644
index 000000000..5dcae0de3
--- /dev/null
+++ b/tutorial/chapter-9/lib/lib.c
@@ -0,0 +1,10 @@
+#include "lib.h"
+
+#ifndef CRUCIAL_DEFINE
+# error CRUCIAL_DEFINE not defined
+#endif
+
+const char *get_string()
+{
+ return "Hello from library";
+}
diff --git a/tutorial/chapter-9/lib/lib.h b/tutorial/chapter-9/lib/lib.h
new file mode 100644
index 000000000..ef39ca4a1
--- /dev/null
+++ b/tutorial/chapter-9/lib/lib.h
@@ -0,0 +1,8 @@
+#ifndef LIB_H
+#define LIB_H
+
+#include "lib_global.h"
+
+MYLIB_EXPORT const char *get_string();
+
+#endif // LIB_H
diff --git a/tutorial/chapter-9/lib/lib.qbs b/tutorial/chapter-9/lib/lib.qbs
new file mode 100644
index 000000000..e8cf9bdb9
--- /dev/null
+++ b/tutorial/chapter-9/lib/lib.qbs
@@ -0,0 +1,10 @@
+MyLibrary {
+ name: "mylib"
+ files: [
+ "lib.c",
+ "lib.h",
+ ]
+ Depends { name: 'cpp' }
+ cpp.defines: ['CRUCIAL_DEFINE']
+}
+
diff --git a/tutorial/chapter-9/lib/lib_global.h b/tutorial/chapter-9/lib/lib_global.h
new file mode 100644
index 000000000..329b62fd3
--- /dev/null
+++ b/tutorial/chapter-9/lib/lib_global.h
@@ -0,0 +1,25 @@
+#ifndef LIB_GLOBAL_H
+#define LIB_GLOBAL_H
+
+#if defined(_WIN32) || defined(WIN32)
+#define MY_LIB_DECL_EXPORT __declspec(dllexport)
+#define MY_LIB_DECL_IMPORT __declspec(dllimport)
+#else
+#define MY_LIB_DECL_EXPORT __attribute__((visibility("default")))
+#define MY_LIB_DECL_IMPORT __attribute__((visibility("default")))
+#endif
+
+// ![0]
+// lib/lib_global.h
+#if defined(MYLIB_STATIC_LIBRARY)
+#define MYLIB_EXPORT
+#else
+#if defined(MYLIB_LIBRARY)
+#define MYLIB_EXPORT MY_LIB_DECL_EXPORT
+#else
+#define MYLIB_EXPORT MY_LIB_DECL_IMPORT
+#endif
+#endif
+// ![0]
+
+#endif // LIB_GLOBAL_H
diff --git a/tutorial/chapter-9/myproject.qbs b/tutorial/chapter-9/myproject.qbs
new file mode 100644
index 000000000..fac217b71
--- /dev/null
+++ b/tutorial/chapter-9/myproject.qbs
@@ -0,0 +1,32 @@
+Project {
+ property string version: "1.0.0"
+ property bool withTests: true
+ property bool installDebugInformation: true
+ property stringList autotestArguments: []
+ property stringList autotestWrapper: []
+
+ name: "My Project"
+ minimumQbsVersion: "2.0"
+ //! [0]
+ // myproject.qbs
+ references: [
+ "app/app.qbs",
+ "lib/lib.qbs",
+ "version-header/version-header.qbs",
+ ]
+ //! [0]
+ qbsSearchPaths: "qbs"
+
+ SubProject {
+ filePath: "test/test.qbs"
+ Properties {
+ condition: parent.withTests
+ }
+ }
+
+ AutotestRunner {
+ condition: parent.withTests
+ arguments: parent.autotestArguments
+ wrapper: parent.autotestWrapper
+ }
+}
diff --git a/tutorial/chapter-9/qbs/imports/MyApplication.qbs b/tutorial/chapter-9/qbs/imports/MyApplication.qbs
new file mode 100644
index 000000000..451065190
--- /dev/null
+++ b/tutorial/chapter-9/qbs/imports/MyApplication.qbs
@@ -0,0 +1,10 @@
+CppApplication {
+ Depends { name: "mybuildconfig" }
+ version: mybuildconfig.productVersion
+
+ cpp.rpaths: mybuildconfig.libRPaths
+ consoleApplication: true
+ installDir: mybuildconfig.appInstallDir
+ install: true
+ installDebugInformation: project.installDebugInformation
+}
diff --git a/tutorial/chapter-9/qbs/imports/MyAutoTest.qbs b/tutorial/chapter-9/qbs/imports/MyAutoTest.qbs
new file mode 100644
index 000000000..1faecdc33
--- /dev/null
+++ b/tutorial/chapter-9/qbs/imports/MyAutoTest.qbs
@@ -0,0 +1,3 @@
+MyApplication {
+ type: ["application", "autotest"]
+}
diff --git a/tutorial/chapter-9/qbs/imports/MyLibrary.qbs b/tutorial/chapter-9/qbs/imports/MyLibrary.qbs
new file mode 100644
index 000000000..9f2590f50
--- /dev/null
+++ b/tutorial/chapter-9/qbs/imports/MyLibrary.qbs
@@ -0,0 +1,26 @@
+// ![0]
+Library {
+ Depends { name: "cpp" }
+ Depends { name: "mybuildconfig" }
+ type: mybuildconfig.staticBuild ? "staticlibrary" : "dynamiclibrary"
+ version: mybuildconfig.productVersion
+ install: !mybuildconfig.staticBuild || mybuildconfig.installStaticLib
+ installDir: mybuildconfig.libInstallDir
+
+ readonly property string _nameUpper : name.replace(" ", "_").toUpperCase()
+ property string libraryMacro: _nameUpper + "_LIBRARY"
+ property string staticLibraryMacro: _nameUpper + "_STATIC_LIBRARY"
+ cpp.defines: mybuildconfig.staticBuild ? [staticLibraryMacro] : [libraryMacro]
+ cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: [exportingProduct.sourceDirectory]
+ cpp.defines: exportingProduct.mybuildconfig.staticBuild
+ ? [exportingProduct.staticLibraryMacro] : []
+ }
+
+ Depends { name: "bundle" }
+ bundle.isBundle: false
+}
+// ![0]
diff --git a/tutorial/chapter-9/qbs/modules/mybuildconfig/mybuildconfig.qbs b/tutorial/chapter-9/qbs/modules/mybuildconfig/mybuildconfig.qbs
new file mode 100644
index 000000000..7aed8f7eb
--- /dev/null
+++ b/tutorial/chapter-9/qbs/modules/mybuildconfig/mybuildconfig.qbs
@@ -0,0 +1,30 @@
+import qbs.FileInfo
+
+//! [0]
+// qbs/modules/mybuildconfig/mybuildconfig.qbs
+Module {
+ Depends { name: "cpp" }
+
+ property string productVersion: "1.0.0"
+ // ...
+ //! [0]
+ property string appInstallDir: "bin"
+ property string libDirName: "lib"
+ property string libInstallDir: qbs.targetOS.contains("windows") ? "bin" : libDirName
+ property bool staticBuild: false
+ property bool installStaticLib: true
+ property bool enableRPath: true
+
+ property stringList libRPaths: {
+ if (enableRPath && cpp.rpathOrigin && product.installDir) {
+ return [
+ FileInfo.joinPaths(
+ cpp.rpathOrigin,
+ FileInfo.relativePath(
+ FileInfo.joinPaths('/', product.installDir),
+ FileInfo.joinPaths('/', libDirName)))
+ ];
+ }
+ return [];
+ }
+}
diff --git a/tutorial/chapter-9/test/test.c b/tutorial/chapter-9/test/test.c
new file mode 100644
index 000000000..1fef24685
--- /dev/null
+++ b/tutorial/chapter-9/test/test.c
@@ -0,0 +1,18 @@
+#include "lib.h"
+
+#include <stdio.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+ if (argc > 2) {
+ printf("usage: test [value]\n");
+ return 1;
+ }
+ const char *expected = argc == 2 ? argv[1] : "Hello from library";
+ if (strcmp(get_string(), expected) != 0) {
+ printf("text differs\n");
+ return 1;
+ }
+ return 0;
+}
diff --git a/tutorial/chapter-9/test/test.qbs b/tutorial/chapter-9/test/test.qbs
new file mode 100644
index 000000000..1e911da47
--- /dev/null
+++ b/tutorial/chapter-9/test/test.qbs
@@ -0,0 +1,5 @@
+MyAutoTest {
+ Depends { name: "mylib" }
+ name: "mytest"
+ files: "test.c"
+}
diff --git a/tutorial/chapter-9/version-header/version-header.qbs b/tutorial/chapter-9/version-header/version-header.qbs
new file mode 100644
index 000000000..0a416d572
--- /dev/null
+++ b/tutorial/chapter-9/version-header/version-header.qbs
@@ -0,0 +1,57 @@
+//! [5]
+// version-header/version-header.qbs
+//! [0]
+import qbs.TextFile
+
+Product {
+ name: "version_header"
+ type: "hpp"
+
+ Depends { name: "mybuildconfig" }
+//! [0]
+
+//! [1]
+ Group {
+ files: ["version.h.in"]
+ fileTags: ["version_h_in"]
+ }
+//! [1]
+
+//! [2]
+ Rule {
+ inputs: ["version_h_in"]
+ Artifact {
+ filePath: "version.h"
+ fileTags: "hpp"
+ }
+//! [2]
+//! [3]
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "generating " + output.fileName;
+ cmd.highlight = "codegen";
+ cmd.sourceCode = function() {
+ var file = new TextFile(input.filePath, TextFile.ReadOnly);
+ var content = file.readAll();
+
+ content = content.replace(
+ "${PRODUCT_VERSION}",
+ product.mybuildconfig.productVersion);
+
+ file = new TextFile(output.filePath, TextFile.WriteOnly);
+ file.write(content);
+ file.close();
+ }
+ return cmd;
+ }
+//! [3]
+ }
+
+//! [4]
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: exportingProduct.buildDirectory
+ }
+//! [4]
+}
+//! [5]
diff --git a/tutorial/chapter-9/version-header/version.h.in b/tutorial/chapter-9/version-header/version.h.in
new file mode 100644
index 000000000..2e6604967
--- /dev/null
+++ b/tutorial/chapter-9/version-header/version.h.in
@@ -0,0 +1,9 @@
+//! [0]
+// version-header/version.h.in
+#ifndef VERSION_H
+#define VERSION_H
+
+const char kProductVersion[] = "${PRODUCT_VERSION}";
+
+#endif // VERSION_H
+//! [0]
diff --git a/tutorial/tutorial.qbs b/tutorial/tutorial.qbs
new file mode 100644
index 000000000..0d2ae3514
--- /dev/null
+++ b/tutorial/tutorial.qbs
@@ -0,0 +1,5 @@
+Product {
+ files: [
+ "*/**",
+ ]
+}