aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml30
-rw-r--r--.github/workflows/release.yml3
-rw-r--r--VERSION2
-rw-r--r--cmake/QbsBuildConfig.cmake3
-rw-r--r--doc/qbs.qdoc181
-rw-r--r--doc/reference/items/language/depends.qdoc14
-rw-r--r--doc/reference/items/language/module.qdoc57
-rw-r--r--doc/reference/items/language/properties.qdoc2
-rw-r--r--doc/reference/module-providers/qbspkgconfig-module-provider.qdoc6
-rw-r--r--docker-compose.yml43
-rw-r--r--docker/focal/test-android.Dockerfile8
-rwxr-xr-xscripts/install-qt.sh9
-rw-r--r--share/qbs/module-providers/Qt/provider.qbs2
-rw-r--r--share/qbs/module-providers/Qt/setup-qt.js20
-rw-r--r--share/qbs/module-providers/Qt/templates/QtModule.qbs1
-rw-r--r--share/qbs/module-providers/Qt/templates/android_support.qbs2
-rw-r--r--share/qbs/module-providers/qbspkgconfig.qbs10
-rw-r--r--share/qbs/modules/Android/sdk/sdk.qbs32
-rw-r--r--share/qbs/modules/bundle/BundleModule.qbs86
-rw-r--r--share/qbs/modules/cpp/GenericGCC.qbs2
-rw-r--r--share/qbs/modules/qbs/common.qbs2
-rw-r--r--src/app/config/configcommandlineparser.cpp2
-rw-r--r--src/app/qbs-create-project/createproject.cpp8
-rw-r--r--src/app/qbs-setup-android/android-setup.cpp2
-rw-r--r--src/app/qbs-setup-toolchains/gccprobe.cpp4
-rw-r--r--src/app/qbs-setup-toolchains/msvcprobe.cpp4
-rw-r--r--src/app/qbs-setup-toolchains/xcodeprobe.cpp4
-rw-r--r--src/app/qbs/commandlinefrontend.cpp14
-rw-r--r--src/app/qbs/parser/commandlineoption.cpp2
-rw-r--r--src/app/qbs/parser/commandlineparser.cpp6
-rw-r--r--src/app/qbs/parser/parsercommand.cpp2
-rw-r--r--src/app/qbs/status.cpp9
-rw-r--r--src/lib/corelib/CMakeLists.txt67
-rw-r--r--src/lib/corelib/api/internaljobs.cpp10
-rw-r--r--src/lib/corelib/api/languageinfo.cpp12
-rw-r--r--src/lib/corelib/api/project.cpp31
-rw-r--r--src/lib/corelib/api/projectdata.cpp16
-rw-r--r--src/lib/corelib/api/projectfileupdater.cpp4
-rw-r--r--src/lib/corelib/buildgraph/artifactsscriptvalue.cpp2
-rw-r--r--src/lib/corelib/buildgraph/artifactvisitor.cpp2
-rw-r--r--src/lib/corelib/buildgraph/buildgraph.cpp24
-rw-r--r--src/lib/corelib/buildgraph/buildgraphloader.cpp70
-rw-r--r--src/lib/corelib/buildgraph/buildgraphnode.cpp6
-rw-r--r--src/lib/corelib/buildgraph/cycledetector.cpp2
-rw-r--r--src/lib/corelib/buildgraph/environmentscriptrunner.cpp2
-rw-r--r--src/lib/corelib/buildgraph/executor.cpp48
-rw-r--r--src/lib/corelib/buildgraph/inputartifactscanner.cpp8
-rw-r--r--src/lib/corelib/buildgraph/processcommandexecutor.cpp2
-rw-r--r--src/lib/corelib/buildgraph/productinstaller.cpp4
-rw-r--r--src/lib/corelib/buildgraph/projectbuilddata.cpp10
-rw-r--r--src/lib/corelib/buildgraph/rulegraph.cpp14
-rw-r--r--src/lib/corelib/buildgraph/rulenode.cpp6
-rw-r--r--src/lib/corelib/buildgraph/rulesapplicator.cpp8
-rw-r--r--src/lib/corelib/buildgraph/transformerchangetracking.cpp10
-rw-r--r--src/lib/corelib/corelib.qbs89
-rw-r--r--src/lib/corelib/generators/generator.cpp15
-rw-r--r--src/lib/corelib/generators/generatorutils.cpp2
-rw-r--r--src/lib/corelib/jsextensions/moduleproperties.cpp2
-rw-r--r--src/lib/corelib/language/builtindeclarations.cpp6
-rw-r--r--src/lib/corelib/language/evaluator.cpp161
-rw-r--r--src/lib/corelib/language/evaluator.h14
-rw-r--r--src/lib/corelib/language/item.cpp77
-rw-r--r--src/lib/corelib/language/item.h49
-rw-r--r--src/lib/corelib/language/itemreader.cpp152
-rw-r--r--src/lib/corelib/language/itemtype.h1
-rw-r--r--src/lib/corelib/language/language.cpp42
-rw-r--r--src/lib/corelib/language/language.h6
-rw-r--r--src/lib/corelib/language/loader.cpp213
-rw-r--r--src/lib/corelib/language/moduleloader.cpp3825
-rw-r--r--src/lib/corelib/language/moduleloader.h459
-rw-r--r--src/lib/corelib/language/modulemerger.cpp267
-rw-r--r--src/lib/corelib/language/moduleproviderinfo.h3
-rw-r--r--src/lib/corelib/language/projectresolver.h208
-rw-r--r--src/lib/corelib/language/propertydeclaration.cpp145
-rw-r--r--src/lib/corelib/language/propertydeclaration.h7
-rw-r--r--src/lib/corelib/language/scriptengine.cpp37
-rw-r--r--src/lib/corelib/language/scriptengine.h1
-rw-r--r--src/lib/corelib/language/value.cpp99
-rw-r--r--src/lib/corelib/language/value.h90
-rw-r--r--src/lib/corelib/loader/astimportshandler.cpp (renamed from src/lib/corelib/language/astimportshandler.cpp)10
-rw-r--r--src/lib/corelib/loader/astimportshandler.h (renamed from src/lib/corelib/language/astimportshandler.h)2
-rw-r--r--src/lib/corelib/loader/astpropertiesitemhandler.cpp (renamed from src/lib/corelib/language/astpropertiesitemhandler.cpp)6
-rw-r--r--src/lib/corelib/loader/astpropertiesitemhandler.h (renamed from src/lib/corelib/language/astpropertiesitemhandler.h)0
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.cpp1144
-rw-r--r--src/lib/corelib/loader/dependenciesresolver.h91
-rw-r--r--src/lib/corelib/loader/groupshandler.cpp316
-rw-r--r--src/lib/corelib/loader/groupshandler.h78
-rw-r--r--src/lib/corelib/loader/itemreader.cpp270
-rw-r--r--src/lib/corelib/loader/itemreader.h (renamed from src/lib/corelib/language/itemreader.h)41
-rw-r--r--src/lib/corelib/loader/itemreaderastvisitor.cpp (renamed from src/lib/corelib/language/itemreaderastvisitor.cpp)22
-rw-r--r--src/lib/corelib/loader/itemreaderastvisitor.h (renamed from src/lib/corelib/language/itemreaderastvisitor.h)6
-rw-r--r--src/lib/corelib/loader/itemreadervisitorstate.cpp (renamed from src/lib/corelib/language/itemreadervisitorstate.cpp)4
-rw-r--r--src/lib/corelib/loader/itemreadervisitorstate.h (renamed from src/lib/corelib/language/itemreadervisitorstate.h)3
-rw-r--r--src/lib/corelib/loader/loaderutils.cpp196
-rw-r--r--src/lib/corelib/loader/loaderutils.h179
-rw-r--r--src/lib/corelib/loader/localprofiles.cpp160
-rw-r--r--src/lib/corelib/loader/localprofiles.h65
-rw-r--r--src/lib/corelib/loader/moduleinstantiator.cpp339
-rw-r--r--src/lib/corelib/loader/moduleinstantiator.h96
-rw-r--r--src/lib/corelib/loader/moduleloader.cpp536
-rw-r--r--src/lib/corelib/loader/moduleloader.h101
-rw-r--r--src/lib/corelib/loader/modulepropertymerger.cpp302
-rw-r--r--src/lib/corelib/loader/modulepropertymerger.h96
-rw-r--r--src/lib/corelib/loader/moduleproviderloader.cpp (renamed from src/lib/corelib/language/moduleproviderloader.cpp)122
-rw-r--r--src/lib/corelib/loader/moduleproviderloader.h (renamed from src/lib/corelib/language/moduleproviderloader.h)64
-rw-r--r--src/lib/corelib/loader/probesresolver.cpp (renamed from src/lib/corelib/language/probesresolver.cpp)87
-rw-r--r--src/lib/corelib/loader/probesresolver.h (renamed from src/lib/corelib/language/probesresolver.h)34
-rw-r--r--src/lib/corelib/loader/productitemmultiplexer.cpp280
-rw-r--r--src/lib/corelib/loader/productitemmultiplexer.h80
-rw-r--r--src/lib/corelib/loader/productscollector.cpp736
-rw-r--r--src/lib/corelib/loader/productscollector.h64
-rw-r--r--src/lib/corelib/loader/productshandler.cpp336
-rw-r--r--src/lib/corelib/loader/productshandler.h66
-rw-r--r--src/lib/corelib/loader/projectresolver.cpp (renamed from src/lib/corelib/language/projectresolver.cpp)1218
-rw-r--r--src/lib/corelib/loader/projectresolver.h (renamed from src/lib/corelib/language/loader.h)44
-rw-r--r--src/lib/corelib/loader/projecttreebuilder.cpp297
-rw-r--r--src/lib/corelib/loader/projecttreebuilder.h (renamed from src/lib/corelib/language/modulemerger.h)75
-rw-r--r--src/lib/corelib/parser/qmlerror.cpp22
-rw-r--r--src/lib/corelib/parser/qmlerror.h4
-rw-r--r--src/lib/corelib/tools/error.cpp2
-rw-r--r--src/lib/corelib/tools/executablefinder.cpp4
-rw-r--r--src/lib/corelib/tools/fileinfo.h8
-rw-r--r--src/lib/corelib/tools/launchersocket.cpp2
-rw-r--r--src/lib/corelib/tools/persistence.cpp2
-rw-r--r--src/lib/corelib/tools/pimpl.h61
-rw-r--r--src/lib/corelib/tools/propagate_const.h418
-rw-r--r--src/lib/corelib/tools/qbs_export.h4
-rw-r--r--src/lib/corelib/tools/qbspluginmanager.cpp2
-rw-r--r--src/lib/corelib/tools/scripttools.cpp29
-rw-r--r--src/lib/corelib/tools/scripttools.h1
-rw-r--r--src/lib/corelib/tools/settingsmodel.cpp6
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.cpp51
-rw-r--r--src/lib/corelib/tools/setupprojectparameters.h1
-rw-r--r--src/lib/corelib/tools/stlutils.h16
-rw-r--r--src/lib/msbuild/solution/visualstudiosolution.cpp4
-rw-r--r--src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp2
-rw-r--r--src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp2
-rw-r--r--src/plugins/generator/makefilegenerator/makefilegenerator.cpp6
-rw-r--r--src/plugins/generator/visualstudio/visualstudiogenerator.cpp2
-rw-r--r--src/shared/quickjs/quickjs.c28
-rw-r--r--src/shared/quickjs/quickjs.h4
-rw-r--r--tests/auto/api/tst_api.cpp39
-rw-r--r--tests/auto/auto.qbs1
-rw-r--r--tests/auto/blackbox/CMakeLists.txt8
-rw-r--r--tests/auto/blackbox/blackbox-providers.qbs21
-rw-r--r--tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs5
-rw-r--r--tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/ByteArray-Info.plist11
-rw-r--r--tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/byteArrayInfoPlist.qbs37
-rw-r--r--tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/main.c1
-rw-r--r--tests/auto/blackbox/testdata-providers/fallback-module-provider/fallback-module-provider.qbs (renamed from tests/auto/blackbox/testdata/fallback-module-provider/fallback-module-provider.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/fallback-module-provider/libdir/qbsmetatestmodule.pc (renamed from tests/auto/blackbox/testdata/fallback-module-provider/libdir/qbsmetatestmodule.pc)0
-rw-r--r--tests/auto/blackbox/testdata-providers/fallback-module-provider/main.cpp (renamed from tests/auto/blackbox/testdata/fallback-module-provider/main.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-providers/module-providers-cache/module-providers-cache.qbs11
-rw-r--r--tests/auto/blackbox/testdata-providers/module-providers-cache/module-providers/provider_a.qbs9
-rw-r--r--tests/auto/blackbox/testdata-providers/module-providers/main.cpp (renamed from tests/auto/blackbox/testdata/module-providers/main.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-providers/module-providers/module-providers.qbs (renamed from tests/auto/blackbox/testdata/module-providers/module-providers.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/module-providers/module-providers/mygenerator/provider.qbs (renamed from tests/auto/blackbox/testdata/module-providers/module-providers/mygenerator/provider.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/module-providers/module-providers/othergenerator/provider.qbs (renamed from tests/auto/blackbox/testdata/module-providers/module-providers/othergenerator/provider.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/probe-in-module-provider/module-providers/provider_a.qbs (renamed from tests/auto/blackbox/testdata/probe-in-module-provider/module-providers/provider_a.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/probe-in-module-provider/probe-in-module-provider.qbs (renamed from tests/auto/blackbox/testdata/probe-in-module-provider/probe-in-module-provider.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/providers-properties/module-providers/provider_a.qbs (renamed from tests/auto/blackbox/testdata/providers-properties/module-providers/provider_a.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/providers-properties/module-providers/provider_b.qbs (renamed from tests/auto/blackbox/testdata/providers-properties/module-providers/provider_b.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/providers-properties/providers-properties.qbs (renamed from tests/auto/blackbox/testdata/providers-properties/providers-properties.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-properties-in-providers/module-providers/provider_a.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-properties-in-providers/module-providers/provider_a.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs)4
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/module-providers/provider_a.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers-cli-override/module-providers/provider_a.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/module-providers/provider_b.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers-cli-override/module-providers/provider_b.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/qbs-module-providers-cli-override.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers-cli-override/qbs-module-providers-cli-override.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/module-providers/named_provider.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers-compatibility/module-providers/named_provider.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/module-providers/qbsmetatestmodule/provider.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers-compatibility/module-providers/qbsmetatestmodule/provider.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/qbs-module-providers-compatibility.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers-compatibility/qbs-module-providers-compatibility.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers-helpers.js (renamed from tests/auto/blackbox/testdata/qbs-module-providers-helpers.js)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers/module-providers/provider_a.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers/module-providers/provider_a.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers/module-providers/provider_b.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers/module-providers/provider_b.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbs-module-providers/qbs-module-providers.qbs (renamed from tests/auto/blackbox/testdata/qbs-module-providers/qbs-module-providers.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libA.cpp (renamed from tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libA.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libA.h (renamed from tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libA.h)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libs.qbs (renamed from tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libs.qbs)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/main.cpp (renamed from tests/auto/blackbox/testdata/qbspkgconfig-module-provider/main.cpp)0
-rw-r--r--tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/qbspkgconfig-module-provider.qbs (renamed from tests/auto/blackbox/testdata/qbspkgconfig-module-provider/qbspkgconfig-module-provider.qbs)0
-rw-r--r--tests/auto/blackbox/testdata/conflicting-property-values/conflicting-property-values.qbs41
-rw-r--r--tests/auto/blackbox/testdata/date-property/date-property.qbs18
-rw-r--r--tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs2
-rw-r--r--tests/auto/blackbox/testdata/exports-qbs/lib.qbs2
-rw-r--r--tests/auto/blackbox/tst_blackbox.cpp372
-rw-r--r--tests/auto/blackbox/tst_blackbox.h16
-rw-r--r--tests/auto/blackbox/tst_blackboxandroid.cpp53
-rw-r--r--tests/auto/blackbox/tst_blackboxapple.cpp17
-rw-r--r--tests/auto/blackbox/tst_blackboxapple.h1
-rw-r--r--tests/auto/blackbox/tst_blackboxjava.cpp2
-rw-r--r--tests/auto/blackbox/tst_blackboxproviders.cpp368
-rw-r--r--tests/auto/blackbox/tst_blackboxproviders.h60
-rw-r--r--tests/auto/blackbox/tst_blackboxwindows.cpp4
-rw-r--r--tests/auto/language/testdata/duplicate-multiplex-value.qbs (renamed from tests/auto/language/testdata/erroneous/duplicate-multiplex-value.qbs)0
-rw-r--r--tests/auto/language/testdata/duplicate-multiplex-value2.qbs (renamed from tests/auto/language/testdata/erroneous/duplicate-multiplex-value2.qbs)0
-rw-r--r--tests/auto/language/testdata/module-depends-on-product.qbs (renamed from tests/auto/language/testdata/erroneous/module-depends-on-product.qbs)0
-rw-r--r--tests/auto/language/testdata/modulepropertiesingroups.qbs9
-rw-r--r--tests/auto/language/testdata/modules/broken/broken.qbs2
-rw-r--r--tests/auto/language/testdata/modules/module-with-product-dependency/module-with-product-dependency.qbs (renamed from tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs)0
-rw-r--r--tests/auto/language/tst_language.cpp495
-rw-r--r--tests/auto/language/tst_language.h39
-rw-r--r--tests/auto/shared.h2
202 files changed, 9481 insertions, 7316 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d81325349..3006be6c7 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -344,30 +344,12 @@ jobs:
script: './scripts/test-qt.sh',
}
- {
- name: 'Run Android tests (Qt 5.13)',
- image: 'focal-android-513',
- profile: '',
- script: './scripts/test-qt-for-android.sh',
- }
- - {
- name: 'Run Android tests (Qt 5.14)',
- image: 'focal-android-514',
- profile: '',
- script: './scripts/test-qt-for-android.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.0.0)',
- image: 'focal-android-600',
- profile: '',
- script: './scripts/test-qt-for-android.sh',
- }
- - {
name: 'Run Android tests (Qt 6.2.0)',
image: 'focal-android-620',
profile: '',
@@ -380,6 +362,18 @@ jobs:
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',
+ profile: '',
+ script: './scripts/test-qt-for-android.sh',
+ }
+ - {
name: 'Run Android tests (ndk r19c)',
image: 'focal-android-ndk-r19c',
profile: '',
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ea8c401ff..6b63097f6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -21,6 +21,7 @@ jobs:
options: 'qbs.installPrefix:""
modules.cpp.compilerWrapper:ccache
modules.qbsbuildconfig.enableAddressSanitizer:false
+ project.withTests:false
modules.qbsbuildconfig.enableUnitTests:false
modules.qbsbuildconfig.enableBundledQt:true
products.qbs_archive.targetName:qbs-linux-${{ github.run_id }}',
@@ -70,6 +71,7 @@ jobs:
BUILD_OPTIONS: |
qbs.installPrefix:""
modules.cpp.compilerWrapper:ccache
+ project.withTests:false
modules.qbsbuildconfig.enableUnitTests:false
modules.qbsbuildconfig.enableAddressSanitizer:false
modules.qbsbuildconfig.enableBundledQt:true
@@ -150,6 +152,7 @@ jobs:
-p dist
qbs.buildVariant:release
modules.cpp.compilerWrapper:clcache
+ project.withTests:false
modules.qbsbuildconfig.enableBundledQt:true
modules.qbsbuildconfig.enableUnitTests:false
modules.cpp.treatWarningsAsErrors:true
diff --git a/VERSION b/VERSION
index e9307ca57..7ec1d6db4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.0.2
+2.1.0
diff --git a/cmake/QbsBuildConfig.cmake b/cmake/QbsBuildConfig.cmake
index 4e49cf4b3..e0a7d76d7 100644
--- a/cmake/QbsBuildConfig.cmake
+++ b/cmake/QbsBuildConfig.cmake
@@ -85,6 +85,9 @@ set(DEFAULT_DEFINES "")
if(WIN32)
list(APPEND DEFAULT_DEFINES UNICODE _UNICODE _SCL_SECURE_NO_WARNINGS)
endif()
+if(WITH_TESTS)
+ list(APPEND DEFAULT_DEFINES QBS_WITH_TESTS)
+endif()
# CMake 3.10 doesn't have list(TRANSFORM)
function(list_transform_prepend var prefix)
diff --git a/doc/qbs.qdoc b/doc/qbs.qdoc
index 45d6dfc91..c77dae824 100644
--- a/doc/qbs.qdoc
+++ b/doc/qbs.qdoc
@@ -67,6 +67,7 @@
\li \l{Generators}
\li \l{Multiplexing}
\li \l{Custom Modules and Items}
+ \li \l{Special Property Values}
\li \l{Module Providers}
\endlist
\li \l{How-tos}
@@ -1061,6 +1062,7 @@
\li \l{Generators}
\li \l{Multiplexing}
\li \l{Custom Modules and Items}
+ \li \l{Special Property Values}
\li \l{Module Providers}
\endlist
@@ -1207,27 +1209,17 @@
}
\endcode
- A module can implicitly depend on other modules. For example, the
- \l{Qt.core} module depends on the \l{cpp} module. However, to set the
- properties of a module, you must make the dependency explicit.
+ A module can depend on other modules. For example, the
+ \l{Qt.core} module depends on the \l{cpp} module. The module dependencies are transitive,
+ i.e. in a Product, all dependent modules are accessible:
\code
- // THIS DOES NOT WORK
Application {
name: "helloworld"
files: ["main.cpp"]
Depends { name: "Qt.core" }
- cpp.optimization: "ludicrousSpeed"
- // ERROR! We do not know about "cpp" here,
- // though "Qt.core" depends on "cpp".
- }
-
- // THIS WORKS
- Application {
- name: "helloworld"
- files: ["main.cpp"]
- Depends { name: "Qt.core" }
- Depends { name: "cpp" }
+ // the "cpp" module is available since
+ // "Qt.core" depends on "cpp".
cpp.optimization: "ludicrousSpeed"
}
\endcode
@@ -1659,7 +1651,7 @@
/*!
\previouspage multiplexing.html
\page custom-modules.html
- \nextpage module-providers.html
+ \nextpage special-property-values.html
\title Custom Modules and Items
@@ -1731,6 +1723,163 @@
/*!
\previouspage custom-modules.html
+ \page special-property-values.html
+ \nextpage module-providers.html
+
+ \title Special Property Values
+
+ Depending on the context, \QBS provides the following special values for use in property
+ bindings and JavaScript code:
+
+ \list
+ \li \l base
+ \li \l exportingProduct
+ \li \l filePath
+ \li \l importingProduct
+ \li \l original
+ \li \l outer
+ \li \l path
+ \li \l product
+ \li \l project
+ \endlist
+
+ \section2 \c base
+ This value is useful when making use of inheritance. It stands for the value of the respective
+ property in the item one level up in the inheritance chain. For instance:
+ \code
+ Product { // defined in MyProduct.qbs
+ Depends { name: "mymodule" }
+ mymodule.someProperty: ["value1"]
+ }
+ ------ some other file ------
+ MyProduct {
+ mymodule.someProperty: base.concat(["value2"]) // => ["value1", "value2"]
+ }
+ \endcode
+
+ \section2 \c exportingProduct
+ Within an \l Export item, you can use the \c exportingProduct variable to refer to
+ the product which defines the Export item:
+
+ \code
+ Product {
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: exportingProduct.sourceDirectory
+ }
+ }
+ \endcode
+
+ \section2 \c filePath
+
+ This value holds the full file path to the \c .qbs file it appears in. This property is
+ rarely used, but might be useful when debugging:
+ \code
+ Product {
+ property bool dummy: {
+ console.info("I'm located at " + filePath);
+ }
+ }
+ \endcode
+
+ \section2 \c importingProduct
+ Within an \l Export item, you can use the \c importingProduct variable to refer to
+ the product that pulls in the resulting module:
+
+ \code
+ Product {
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: importingProduct.buildDirectory
+ }
+ }
+ \endcode
+ Usually, you should use the \l product variable instead for consistency with \l Module items.
+
+ \section2 \c original
+ On the right-hand side of a module property binding, this refers to the value of the property
+ in the module itself (possibly overridden from a profile). Use it to set a module property
+ conditionally:
+ \code
+ Module { // This is mymodule
+ property string aProperty: "z"
+ }
+ ----------
+ Product {
+ Depends { name: "mymodule" }
+ Depends { name: "myothermodule" }
+ // "y" if myothermodule.anotherProperty is "x", "z" otherwise:
+ mymodule.aProperty: myothermodule.anotherProperty === "x" ? "y" : original
+ }
+ \endcode
+
+ \section2 \c outer
+ This value is used in nested items, where it refers to the value of the respective property
+ in the surrounding item. It is only valid in \l{Group} and \l{Properties} items:
+ \code
+ Product {
+ Depends { name: "mymodule" }
+ mymodule.someProperty: ["value1"]
+ Group {
+ name: "special files"
+ files: ["somefile1", "somefile2"]
+ mymodule.someProperty: outer.concat(["value"]) // => ["value1", "value2"]
+ }
+ }
+ \endcode
+
+ \section2 \c path
+
+ This value holds the path to the folder where the \c .qbs file is located. Use it to e.g. add
+ the product's directory to file paths:
+ \code
+ Product {
+ Depends { name: "cpp" }
+ cpp.includePaths: path
+ }
+ \endcode
+
+ \section2 \c product
+
+ This value holds the properties of the product that contains the current item or pulls in the
+ current module:
+ \code
+ Module {
+ Rule {
+ Artifact {
+ fileTags: product.type
+ filePath: {
+ var result = input.fileName;
+ // module properties are available as well
+ if (product.qbs.buildVariant === "debug")
+ result = result + "_debug";
+ result = result + ".out";
+ return result;
+ }
+ }
+ }
+ }
+ \endcode
+ Within the \l Export item, same as \l importingProduct.
+
+ \section2 \c project
+ This value holds the properties of the project that references the current item or pulls in the
+ current module:
+ \code
+ Project {
+ property bool enableProduct: true
+ Product {
+ name: "theProduct"
+ condition: project.enableProduct
+ }
+ }
+ \endcode
+ 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.
+*/
+
+/*!
+ \previouspage special-property-values.html
\page module-providers.html
\nextpage howtos.html
diff --git a/doc/reference/items/language/depends.qdoc b/doc/reference/items/language/depends.qdoc
index 0adba1474..7cc270483 100644
--- a/doc/reference/items/language/depends.qdoc
+++ b/doc/reference/items/language/depends.qdoc
@@ -127,7 +127,19 @@
The \l required and \l profiles properties are ignored if this property is
set.
- Product types attached via Module::additionalProductTypes are not considered.
+ \note This property does not work recursively. Consider this example:
+ \code
+ Product {
+ name: "A"
+ type: "x"
+ Depends { productTypes: "someTag" }
+ }
+ Product {
+ name: "B"
+ Depends { productTypes: "x" }
+ }
+ \endcode
+ It is not guaranteed that \c A will show up in \c B's dependencies.
\nodefaultvalue
*/
diff --git a/doc/reference/items/language/module.qdoc b/doc/reference/items/language/module.qdoc
index 172939537..48ad583c8 100644
--- a/doc/reference/items/language/module.qdoc
+++ b/doc/reference/items/language/module.qdoc
@@ -173,60 +173,11 @@
}
\endqml
- \section1 Special Property Values
-
- For every property defined in a module, \QBS provides the following special
- built-in values:
-
- \list
- \li \l base
- \li \l original
- \li \l outer
- \endlist
-
- \section2 \c base
- This value is useful when making use of inheritance. It stands for the value of the respective
- property in the item one level up in the inheritance chain. For instance:
- \code
- Product { // defined in MyProduct.qbs
- Depends { name: "mymodule" }
- mymodule.someProperty: ["value1"]
- }
- ------ some other file ------
- MyProduct {
- mymodule.someProperty: base.concat(["value2"]) // => ["value1", "value2"]
- }
- \endcode
-
- \section2 \c original
- This is the value of the property in the module itself (possibly overridden from a profile or
- the command line). Use it to set a module property conditionally:
- \code
- Module { // This is mymodule
- property string aProperty: "z"
- }
- ----------
- Product {
- Depends { name: "mymodule" }
- Depends { name: "myothermodule" }
- mymodule.aProperty: myothermodule.anotherProperty === "x" ? "y" : original // => "y" if myothermodule.anotherProperty is "x", "z" otherwise
- \endcode
-
- \section2 \c outer
- This value is used in nested items, where it refers to the value of the respective property
- in the surrounding item. It is only valid in \l{Group} and \l{Properties} items:
- \code
- Product {
- Depends { name: "mymodule" }
- mymodule.someProperty: ["value1"]
- Group {
- name: "special files"
- files: ["somefile1", "somefile2"]
- mymodule.someProperty: outer.concat(["value"]) // => ["value1", "value2"]
- }
- }
- \endcode
+ \section2 Special Property Values
+ For every property defined in a module, \QBS provides the special
+ \l{Special Property Values#original}{original} value containing the value of the property in
+ the module itself (possibly overridden from a profile).
\section1 Dependency Parameters
diff --git a/doc/reference/items/language/properties.qdoc b/doc/reference/items/language/properties.qdoc
index 49da1dee1..3a71a035b 100644
--- a/doc/reference/items/language/properties.qdoc
+++ b/doc/reference/items/language/properties.qdoc
@@ -89,7 +89,7 @@
}
\endcode
- In Properties items, one can access the \l{Module#outer}{outer value} of a
+ In Properties items, one can access the \l{Special Property Values#outer}{outer} value of a
property.
\code
Product {
diff --git a/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc b/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc
index debaa5992..f9c4e9ade 100644
--- a/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc
+++ b/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc
@@ -116,9 +116,7 @@
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. The latter approach gives
- \QBS more information about dependencies, but may have performance implications during resolve
- phase, e.g. when using ABSEIL library.
+ may depend on other modules and property merging is done by \QBS.
- \defaultvalue \c true
+ \defaultvalue \c false
*/
diff --git a/docker-compose.yml b/docker-compose.yml
index 23a27204d..8d7a78dac 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -54,71 +54,60 @@ services:
QT_VERSION: 6.3.1
QTCREATOR_VERSION: 5.0.3
- focal-android-513:
- << : *linux
- hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-5.13.2-4
- build:
- dockerfile: docker/focal/test-android.Dockerfile
- context: .
- args:
- QT_VERSION: 5.13.2
- ANDROID_NDK_VERSION: 23.0.7599858
-
- focal-android-514:
+ focal-android-515:
<< : *linux
hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-5.14.0-4
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-5.15.1-4
build:
dockerfile: docker/focal/test-android.Dockerfile
context: .
args:
- QT_VERSION: 5.14.0
+ QT_VERSION: 5.15.1
ANDROID_NDK_VERSION: 23.0.7599858
- focal-android-515:
+ focal-android-620:
<< : *linux
hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-5.15.1-4
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.2.0-4
build:
dockerfile: docker/focal/test-android.Dockerfile
context: .
args:
- QT_VERSION: 5.15.1
+ QT_VERSION: 6.2.0
ANDROID_NDK_VERSION: 23.0.7599858
- focal-android-600:
+ focal-android-630:
<< : *linux
hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.0.0-4
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.3.0-0
build:
dockerfile: docker/focal/test-android.Dockerfile
context: .
args:
- QT_VERSION: 6.0.0
+ QT_VERSION: 6.3.0
ANDROID_NDK_VERSION: 23.0.7599858
- focal-android-620:
+ focal-android-642:
<< : *linux
hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.2.0-4
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.4.2-0
build:
dockerfile: docker/focal/test-android.Dockerfile
context: .
args:
- QT_VERSION: 6.2.0
+ QT_VERSION: 6.4.2
ANDROID_NDK_VERSION: 23.0.7599858
- focal-android-630:
+ focal-android-650:
<< : *linux
hostname: focal-android
- image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.3.0-0
+ image: ${DOCKER_USER:-qbsbuild}/qbsdev:focal-android-6.5.0-0
build:
dockerfile: docker/focal/test-android.Dockerfile
context: .
args:
- QT_VERSION: 6.3.0
- ANDROID_NDK_VERSION: 23.0.7599858
+ QT_VERSION: 6.5.0
+ ANDROID_NDK_VERSION: 25.1.8937393
focal-android-ndk-r19c:
<< : *linux
diff --git a/docker/focal/test-android.Dockerfile b/docker/focal/test-android.Dockerfile
index 31f4eb773..5f479e4fc 100644
--- a/docker/focal/test-android.Dockerfile
+++ b/docker/focal/test-android.Dockerfile
@@ -37,10 +37,18 @@ RUN apt-get update -qq && \
curl \
libasan5 \
libglib2.0-0 \
+ locales \
openjdk-8-jdk-headless \
p7zip-full \
unzip
+# Set the locale
+RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
+ locale-gen
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
RUN echo "export JAVA_HOME=${JAVA_HOME}" > /etc/profile.d/android.sh && \
echo "export PATH=${JAVA_HOME}/bin:\${PATH}" >> /etc/profile.d/android.sh
diff --git a/scripts/install-qt.sh b/scripts/install-qt.sh
index 840a10532..5799fee02 100755
--- a/scripts/install-qt.sh
+++ b/scripts/install-qt.sh
@@ -356,11 +356,18 @@ for COMPONENT in ${COMPONENTS}; do
if [ "${TARGET_PLATFORM}" == "android" ] && [ ! "${VERSION}" \< "6.0.0" ]; then
CONF_FILE="${UNPACK_DIR}/${VERSION}/${SUBDIR}/bin/target_qt.conf"
+ ANDROID_QMAKE_FILE="${UNPACK_DIR}/${VERSION}/${SUBDIR}/bin/qmake"
+ if [ "${TOOLCHAIN}" == "android_armv7" ] && [ ! "${VERSION}" \< "6.4.2" ]; then
+ sed -i "s/\r//" "${CONF_FILE}"
+ sed -i "s|HostLibraryExecutables=.\/bin|HostLibraryExecutables=.\/libexec|g" "${CONF_FILE}"
+ chmod +x "${ANDROID_QMAKE_FILE}"
+ sed -i "s|\\\|\/|g" "${ANDROID_QMAKE_FILE}"
+ fi
sed -i "s|target|../$TOOLCHAIN|g" "${CONF_FILE}"
sed -i "/HostPrefix/ s|$|gcc_64|g" "${CONF_FILE}"
- ANDROID_QMAKE_FILE="${UNPACK_DIR}/${VERSION}/${SUBDIR}/bin/qmake"
QMAKE_FILE="${UNPACK_DIR}/${VERSION}/gcc_64/bin/qmake"
sed -i "s|\/home\/qt\/work\/install\/bin\/qmake|$QMAKE_FILE|g" "${ANDROID_QMAKE_FILE}"
+ sed -i "s|\/Users\/qt\/work\/install\/bin\/qmake|$QMAKE_FILE|g" "${ANDROID_QMAKE_FILE}"
elif [ "${TARGET_PLATFORM}" == "ios" ] && [ ! "${VERSION}" \< "6.0.0" ]; then
CONF_FILE="${UNPACK_DIR}/${VERSION}/${SUBDIR}/bin/target_qt.conf"
sed -i.bak "s|HostData=target|HostData=../$TOOLCHAIN|g" "${CONF_FILE}"
diff --git a/share/qbs/module-providers/Qt/provider.qbs b/share/qbs/module-providers/Qt/provider.qbs
index 33083c51d..be026b1e0 100644
--- a/share/qbs/module-providers/Qt/provider.qbs
+++ b/share/qbs/module-providers/Qt/provider.qbs
@@ -2,5 +2,5 @@ import "setup-qt.js" as SetupQt
ModuleProvider {
property stringList qmakeFilePaths
- relativeSearchPaths: SetupQt.doSetup(qmakeFilePaths, outputBaseDir, path, qbs)
+ relativeSearchPaths: SetupQt.doSetup(qmakeFilePaths, outputBaseDir, path)
}
diff --git a/share/qbs/module-providers/Qt/setup-qt.js b/share/qbs/module-providers/Qt/setup-qt.js
index 4b8c62ca2..b33b30e3a 100644
--- a/share/qbs/module-providers/Qt/setup-qt.js
+++ b/share/qbs/module-providers/Qt/setup-qt.js
@@ -48,9 +48,8 @@ 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 exeSuffix(qbs) { return FileInfo.executableSuffix(); }
-function getQmakeFilePaths(qmakeFilePaths, qbs) {
+function getQmakeFilePaths(qmakeFilePaths) {
if (qmakeFilePaths && qmakeFilePaths.length > 0)
return qmakeFilePaths;
console.info("Detecting Qt installations...");
@@ -58,9 +57,8 @@ function getQmakeFilePaths(qmakeFilePaths, qbs) {
var pathValue = Environment.getEnv("PATH");
if (pathValue) {
var dirs = splitNonEmpty(pathValue, FileInfo.pathListSeparator());
- var suffix = exeSuffix(qbs);
for (var i = 0; i < dirs.length; ++i) {
- var candidate = FileInfo.joinPaths(dirs[i], "qmake" + suffix);
+ var candidate = FileInfo.joinPaths(dirs[i], "qmake" + FileInfo.executableSuffix());
var canonicalCandidate = FileInfo.canonicalPath(candidate);
if (!canonicalCandidate || !File.exists(canonicalCandidate))
continue;
@@ -248,7 +246,7 @@ function abiToArchitecture(abi) {
}
}
-function getQtProperties(qmakeFilePath, qbs) {
+function getQtProperties(qmakeFilePath) {
var queryResult = queryQmake(qmakeFilePath);
var qtProps = {};
qtProps.installPrefixPath = pathQueryValue(queryResult, "QT_INSTALL_PREFIX");
@@ -1581,10 +1579,10 @@ function copyTemplateFile(fileName, targetDirectory, qtProps, abi, location, all
targetFile.close();
}
-function setupOneQt(qmakeFilePath, outputBaseDir, uniquify, location, qbs) {
+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, qbs);
+ var qtProps = getQtProperties(qmakeFilePath);
var androidAbis = [];
if (qtProps.androidAbis !== undefined)
// Multiple androidAbis detected: Qt >= 5.14
@@ -1657,7 +1655,7 @@ function setupOneQt(qmakeFilePath, outputBaseDir, uniquify, location, qbs) {
allFiles);
var qmlcacheStr = "qmlcache";
if (File.exists(FileInfo.joinPaths(qtProps.qmlLibExecPath,
- "qmlcachegen" + exeSuffix(qbs)))) {
+ "qmlcachegen" + FileInfo.executableSuffix()))) {
copyTemplateFile(qmlcacheStr + ".qbs",
FileInfo.joinPaths(qbsQtModuleBaseDir, qmlcacheStr), qtProps,
androidAbis[a], location, allFiles);
@@ -1687,8 +1685,8 @@ function setupOneQt(qmakeFilePath, outputBaseDir, uniquify, location, qbs) {
return relativeSearchPaths;
}
-function doSetup(qmakeFilePaths, outputBaseDir, location, qbs) {
- qmakeFilePaths = getQmakeFilePaths(qmakeFilePaths, qbs);
+function doSetup(qmakeFilePaths, outputBaseDir, location) {
+ qmakeFilePaths = getQmakeFilePaths(qmakeFilePaths);
if (!qmakeFilePaths || qmakeFilePaths.length === 0)
return [];
var uniquifySearchPath = qmakeFilePaths.length > 1;
@@ -1697,7 +1695,7 @@ function doSetup(qmakeFilePaths, outputBaseDir, location, qbs) {
try {
console.info("Setting up Qt at '" + toNative(qmakeFilePaths[i]) + "'...");
var searchPaths = setupOneQt(qmakeFilePaths[i], outputBaseDir, uniquifySearchPath,
- location, qbs);
+ location);
if (searchPaths.length > 0) {
for (var j = 0; j < searchPaths.length; ++j )
allSearchPaths.push(searchPaths[j]);
diff --git a/share/qbs/module-providers/Qt/templates/QtModule.qbs b/share/qbs/module-providers/Qt/templates/QtModule.qbs
index 62e05327b..35421436f 100644
--- a/share/qbs/module-providers/Qt/templates/QtModule.qbs
+++ b/share/qbs/module-providers/Qt/templates/QtModule.qbs
@@ -23,6 +23,7 @@ Module {
// We have to pull in all plugins here, because dependency resolving happens
// before module merging, and we don't know yet if someone set
// Qt.pluginSupport.pluginsByType in the product.
+ // TODO: We might be able to use Qt.pluginSupport.pluginsByType now.
// The real filtering is done later by the plugin module files themselves.
var list = [];
var allPlugins = Qt.plugin_support.allPluginsByType;
diff --git a/share/qbs/module-providers/Qt/templates/android_support.qbs b/share/qbs/module-providers/Qt/templates/android_support.qbs
index 66914d11b..6d548f194 100644
--- a/share/qbs/module-providers/Qt/templates/android_support.qbs
+++ b/share/qbs/module-providers/Qt/templates/android_support.qbs
@@ -21,7 +21,7 @@ Module {
property string _qtBinaryDir
property string _qtInstallDir
property bool _enableSdkSupport: product.type && product.type.contains("android.package")
- && !consoleApplication
+ && !product.consoleApplication
property bool _enableNdkSupport: !product.aggregate || product.multiplexConfigurationId
property string _templatesBaseDir: FileInfo.joinPaths(_qtInstallDir, "src", "android")
property string _deployQtOutDir: FileInfo.joinPaths(product.buildDirectory, "deployqt_out")
diff --git a/share/qbs/module-providers/qbspkgconfig.qbs b/share/qbs/module-providers/qbspkgconfig.qbs
index 92d5fda71..2736220a8 100644
--- a/share/qbs/module-providers/qbspkgconfig.qbs
+++ b/share/qbs/module-providers/qbspkgconfig.qbs
@@ -52,8 +52,14 @@ ModuleProvider {
property stringList extraPaths
property stringList libDirs
property bool staticMode: false
- property path sysroot: qbs.sysroot
- property bool mergeDependencies: true
+
+ // 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: {
diff --git a/share/qbs/modules/Android/sdk/sdk.qbs b/share/qbs/modules/Android/sdk/sdk.qbs
index d663a01e4..440448dcf 100644
--- a/share/qbs/modules/Android/sdk/sdk.qbs
+++ b/share/qbs/modules/Android/sdk/sdk.qbs
@@ -52,7 +52,7 @@ Module {
Probes.PathProbe {
id: bundletoolProbe
- platformSearchPaths: [Android.sdk.sdkDir]
+ platformSearchPaths: [sdkDir]
names: ["bundletool-all"]
nameSuffixes: ["-0.11.0.jar", "-0.12.0.jar", "-0.13.0.jar", "-0.13.3.jar", "-0.13.4.jar",
"-0.14.0.jar", "-0.15.0.jar", "-1.0.0.jar", "-1.1.0.jar", "-1.2.0.jar", "-1.3.0.jar",
@@ -102,36 +102,36 @@ Module {
Group {
name: "java sources"
- condition: Android.sdk.automaticSources
- prefix: FileInfo.resolvePath(product.sourceDirectory, Android.sdk.sourcesDir + '/')
+ condition: product.Android.sdk.automaticSources
+ prefix: FileInfo.resolvePath(product.sourceDirectory, product.Android.sdk.sourcesDir + '/')
files: "**/*.java"
}
Group {
name: "android resources"
- condition: Android.sdk.automaticSources
+ condition: product.Android.sdk.automaticSources
fileTags: ["android.resources"]
- prefix: FileInfo.resolvePath(product.sourceDirectory, Android.sdk.resourcesDir + '/')
+ prefix: FileInfo.resolvePath(product.sourceDirectory, product.Android.sdk.resourcesDir + '/')
files: "**/*"
}
Group {
name: "android assets"
- condition: Android.sdk.automaticSources
+ condition: product.Android.sdk.automaticSources
fileTags: ["android.assets"]
- prefix: FileInfo.resolvePath(product.sourceDirectory, Android.sdk.assetsDir + '/')
+ prefix: FileInfo.resolvePath(product.sourceDirectory, product.Android.sdk.assetsDir + '/')
files: "**/*"
}
Group {
name: "manifest"
- condition: Android.sdk.automaticSources
+ condition: product.Android.sdk.automaticSources
fileTags: ["android.manifest"]
- files: Android.sdk.manifestFile
- && Android.sdk.manifestFile !== Android.sdk.defaultManifestFile
- ? [Android.sdk.manifestFile]
- : (File.exists(Android.sdk.defaultManifestFile)
- ? [Android.sdk.defaultManifestFile] : [])
+ files: product.Android.sdk.manifestFile
+ && product.Android.sdk.manifestFile !== product.Android.sdk.defaultManifestFile
+ ? [product.Android.sdk.manifestFile]
+ : (File.exists(product.Android.sdk.defaultManifestFile)
+ ? [product.Android.sdk.defaultManifestFile] : [])
}
@@ -196,8 +196,8 @@ Module {
java.languageVersion: platformJavaVersion
java.runtimeVersion: platformJavaVersion
java.bootClassPaths: androidJarFilePath
- codesign.apksignerFilePath: Android.sdk.apksignerFilePath
- codesign._packageName: Android.sdk.apkBaseName + (_generateAab ? ".aab" : ".apk")
+ codesign.apksignerFilePath: apksignerFilePath
+ codesign._packageName: apkBaseName + (_generateAab ? ".aab" : ".apk")
codesign.useApksigner: !_generateAab
}
@@ -263,7 +263,7 @@ Module {
property bool customManifestProcessing: false
Group {
- condition: !Android.sdk.customManifestProcessing
+ condition: !product.Android.sdk.customManifestProcessing
fileTagsFilter: "android.manifest_processed"
fileTags: "android.manifest_final"
}
diff --git a/share/qbs/modules/bundle/BundleModule.qbs b/share/qbs/modules/bundle/BundleModule.qbs
index 0d8452a7e..0c2d7ba6a 100644
--- a/share/qbs/modules/bundle/BundleModule.qbs
+++ b/share/qbs/modules/bundle/BundleModule.qbs
@@ -281,8 +281,8 @@ Module {
outputFileTags: ["bundle.input", "aggregate_infoplist"]
outputArtifacts: {
var artifacts = [];
- var embed = ModUtils.moduleProperty(product, "embedInfoPlist");
- if (ModUtils.moduleProperty(product, "isBundle") || embed) {
+ var embed = product.bundle.embedInfoPlist;
+ if (product.bundle.isBundle || embed) {
artifacts.push({
filePath: FileInfo.joinPaths(
product.destinationDirectory, product.name + "-Info.plist"),
@@ -290,7 +290,7 @@ Module {
bundle: {
_bundleFilePath: FileInfo.joinPaths(
product.destinationDirectory,
- ModUtils.moduleProperty(product, "infoPlistPath")),
+ product.bundle.infoPlistPath),
}
});
}
@@ -301,20 +301,21 @@ Module {
var cmd = new JavaScriptCommand();
cmd.description = "generating Info.plist for " + product.name;
cmd.highlight = "codegen";
- cmd.infoPlist = ModUtils.moduleProperty(product, "infoPlist") || {};
- cmd.processInfoPlist = ModUtils.moduleProperty(product, "processInfoPlist");
- cmd.infoPlistFormat = ModUtils.moduleProperty(product, "infoPlistFormat");
- cmd.extraEnv = ModUtils.moduleProperty(product, "extraEnv");
- cmd.qmakeEnv = ModUtils.moduleProperty(product, "qmakeEnv");
+ cmd.infoPlist = product.bundle.infoPlist || {};
+ cmd.processInfoPlist = product.bundle.processInfoPlist;
+ cmd.infoPlistFormat = product.bundle.infoPlistFormat;
+ cmd.extraEnv = product.bundle.extraEnv;
+ cmd.qmakeEnv = product.bundle.qmakeEnv;
+ // TODO: bundle module should know nothing about cpp module
cmd.buildEnv = product.moduleProperty("cpp", "buildEnv");
- cmd.developerPath = product.moduleProperty("xcode", "developerPath");
- cmd.platformInfoPlist = product.moduleProperty("xcode", "platformInfoPlist");
- cmd.sdkSettingsPlist = product.moduleProperty("xcode", "sdkSettingsPlist");
- cmd.toolchainInfoPlist = product.moduleProperty("xcode", "toolchainInfoPlist");
+ cmd.developerPath = product.xcode.developerPath;
+ cmd.platformInfoPlist = product.xcode.platformInfoPlist;
+ cmd.sdkSettingsPlist = product.xcode.sdkSettingsPlist;
+ cmd.toolchainInfoPlist = product.xcode.toolchainInfoPlist;
- cmd.osBuildVersion = product.moduleProperty("qbs", "hostOSBuildVersion");
+ cmd.osBuildVersion = product.qbs.hostOSBuildVersion;
cmd.sourceCode = function() {
var plist, process, key, i;
@@ -343,7 +344,7 @@ Module {
if (processInfoPlist) {
// Add default values to the aggregate plist if the corresponding keys
// for those values are not already present
- var defaultValues = ModUtils.moduleProperty(product, "defaultInfoPlist");
+ var defaultValues = product.bundle.defaultInfoPlist;
for (key in defaultValues) {
if (defaultValues.hasOwnProperty(key) && !(key in aggregatePlist))
aggregatePlist[key] = defaultValues[key];
@@ -480,11 +481,11 @@ Module {
outputFileTags: ["bundle.input", "pkginfo"]
outputArtifacts: {
var artifacts = [];
- if (ModUtils.moduleProperty(product, "isBundle") && ModUtils.moduleProperty(product, "generatePackageInfo")) {
+ if (product.bundle.isBundle && product.bundle.generatePackageInfo) {
artifacts.push({
filePath: FileInfo.joinPaths(product.destinationDirectory, "PkgInfo"),
fileTags: ["bundle.input", "pkginfo"],
- bundle: { _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "pkgInfoPath")) }
+ bundle: { _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory, product.bundle.pkgInfoPath) }
});
}
return artifacts;
@@ -535,9 +536,9 @@ Module {
"bundle.code-signature"]
outputArtifacts: {
var i, artifacts = [];
- if (ModUtils.moduleProperty(product, "isBundle")) {
+ if (product.bundle.isBundle) {
for (i in inputs["bundle.input"]) {
- var fp = inputs["bundle.input"][i].moduleProperty("bundle", "_bundleFilePath");
+ var fp = inputs["bundle.input"][i].bundle._bundleFilePath;
if (!fp)
throw("Artifact " + inputs["bundle.input"][i].filePath + " has no associated bundle file path");
var extraTags = inputs["bundle.input"][i].fileTags.includes("application")
@@ -552,20 +553,19 @@ Module {
for (i in provprofiles) {
artifacts.push({
filePath: FileInfo.joinPaths(product.destinationDirectory,
- ModUtils.moduleProperty(product,
- "contentsFolderPath"),
+ product.bundle.contentsFolderPath,
provprofiles[i].fileName),
fileTags: ["bundle.provisioningprofile", "bundle.content"]
});
}
- var packageType = ModUtils.moduleProperty(product, "packageType");
- var isShallow = ModUtils.moduleProperty(product, "isShallow");
+ var packageType = product.bundle.packageType;
+ var isShallow = product.bundle.isShallow;
if (packageType === "FMWK" && !isShallow) {
- var publicHeaders = ModUtils.moduleProperty(product, "publicHeaders");
+ var publicHeaders = product.bundle.publicHeaders;
if (publicHeaders && publicHeaders.length) {
artifacts.push({
- filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "bundleName"), "Headers"),
+ filePath: FileInfo.joinPaths(product.destinationDirectory, product.bundle.bundleName, "Headers"),
fileTags: ["bundle.symlink.headers", "bundle.content"]
});
}
@@ -573,23 +573,23 @@ Module {
var privateHeaders = ModUtils.moduleProperty(product, "privateHeaders");
if (privateHeaders && privateHeaders.length) {
artifacts.push({
- filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "bundleName"), "PrivateHeaders"),
+ filePath: FileInfo.joinPaths(product.destinationDirectory, product.bundle.bundleName, "PrivateHeaders"),
fileTags: ["bundle.symlink.private-headers", "bundle.content"]
});
}
artifacts.push({
- filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "bundleName"), "Resources"),
+ filePath: FileInfo.joinPaths(product.destinationDirectory, product.bundle.bundleName, "Resources"),
fileTags: ["bundle.symlink.resources", "bundle.content"]
});
artifacts.push({
- filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "bundleName"), product.targetName),
+ filePath: FileInfo.joinPaths(product.destinationDirectory, product.bundle.bundleName, product.targetName),
fileTags: ["bundle.symlink.executable", "bundle.content"]
});
artifacts.push({
- filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "versionsFolderPath"), "Current"),
+ filePath: FileInfo.joinPaths(product.destinationDirectory, product.bundle.versionsFolderPath, "Current"),
fileTags: ["bundle.symlink.version", "bundle.content"]
});
}
@@ -606,7 +606,7 @@ Module {
}
}
- sources = ModUtils.moduleProperty(product, "resources");
+ sources = product.bundle.resources;
for (i in sources) {
destination = BundleTools.destinationDirectoryForResource(product, {baseDir: FileInfo.path(sources[i]), fileName: FileInfo.fileName(sources[i])});
artifacts.push({
@@ -617,7 +617,7 @@ Module {
var wrapperPath = FileInfo.joinPaths(
product.destinationDirectory,
- ModUtils.moduleProperty(product, "bundleName"));
+ product.bundle.bundleName);
for (var i = 0; i < artifacts.length; ++i)
artifacts[i].bundle = { wrapperPath: wrapperPath };
@@ -634,7 +634,7 @@ Module {
prepare: {
var i, cmd, commands = [];
- var packageType = ModUtils.moduleProperty(product, "packageType");
+ var packageType = product.bundle.packageType;
var bundleType = "bundle";
if (packageType === "APPL")
@@ -652,7 +652,7 @@ Module {
var symlinks = outputs["bundle.symlink.version"];
for (i in symlinks) {
- cmd = new Command("ln", ["-sfn", ModUtils.moduleProperty(product, "frameworkVersion"),
+ cmd = new Command("ln", ["-sfn", product.bundle.frameworkVersion,
symlinks[i].filePath]);
cmd.silent = true;
commands.push(cmd);
@@ -699,8 +699,8 @@ Module {
}
var bundleInputs = sortedArtifactList(inputs["bundle.input"], function (a, b) {
- return a.moduleProperty("bundle", "_bundleFilePath").localeCompare(
- b.moduleProperty("bundle", "_bundleFilePath"));
+ return a.bundle._bundleFilePath.localeCompare(
+ b.bundle._bundleFilePath);
});
var bundleContents = sortedArtifactList(outputs["bundle.content.copied"]);
for (i in bundleContents) {
@@ -733,8 +733,8 @@ Module {
cmd = new JavaScriptCommand();
cmd.description = "copying public headers";
cmd.highlight = "filegen";
- cmd.sources = ModUtils.moduleProperty(product, "publicHeaders");
- cmd.destination = FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "publicHeadersFolderPath"));
+ cmd.sources = product.bundle.publicHeaders;
+ cmd.destination = FileInfo.joinPaths(product.destinationDirectory, product.bundle.publicHeadersFolderPath);
cmd.sourceCode = function() {
var i;
for (var i in sources) {
@@ -747,8 +747,8 @@ Module {
cmd = new JavaScriptCommand();
cmd.description = "copying private headers";
cmd.highlight = "filegen";
- cmd.sources = ModUtils.moduleProperty(product, "privateHeaders");
- cmd.destination = FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "privateHeadersFolderPath"));
+ cmd.sources = product.bundle.privateHeaders;
+ cmd.destination = FileInfo.joinPaths(product.destinationDirectory, product.bundle.privateHeadersFolderPath);
cmd.sourceCode = function() {
var i;
for (var i in sources) {
@@ -761,7 +761,7 @@ Module {
cmd = new JavaScriptCommand();
cmd.description = "copying resources";
cmd.highlight = "filegen";
- cmd.sources = ModUtils.moduleProperty(product, "resources");
+ cmd.sources = product.bundle.resources;
cmd.sourceCode = function() {
var i;
for (var i in sources) {
@@ -772,17 +772,17 @@ Module {
if (cmd.sources && cmd.sources.length)
commands.push(cmd);
- if (product.moduleProperty("qbs", "hostOS").includes("darwin")) {
+ if (product.qbs.hostOS.includes("darwin")) {
Array.prototype.push.apply(commands, Codesign.prepareSign(
project, product, inputs, outputs, input, output));
if (bundleType === "application"
- && product.moduleProperty("qbs", "targetOS").includes("macos")) {
+ && product.qbs.targetOS.includes("macos")) {
var bundlePath = FileInfo.joinPaths(
product.destinationDirectory, product.bundle.bundleName);
- cmd = new Command(ModUtils.moduleProperty(product, "lsregisterPath"),
+ cmd = new Command(product.bundle.lsregisterPath,
["-f", bundlePath]);
- cmd.description = "registering " + ModUtils.moduleProperty(product, "bundleName");
+ cmd.description = "registering " + product.bundle.bundleName;
commands.push(cmd);
}
}
diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs
index 22a98ad57..05ceccf5f 100644
--- a/share/qbs/modules/cpp/GenericGCC.qbs
+++ b/share/qbs/modules/cpp/GenericGCC.qbs
@@ -216,7 +216,7 @@ CppModule {
linkerScriptFlag: "-T"
readonly property bool shouldCreateSymlinks: {
- return createSymlinks && internalVersion && ["macho", "elf"].includes(cpp.imageFormat);
+ return createSymlinks && internalVersion && ["macho", "elf"].includes(imageFormat);
}
readonly property bool shouldSignArtifacts: codesign._canSignArtifacts
diff --git a/share/qbs/modules/qbs/common.qbs b/share/qbs/modules/qbs/common.qbs
index 7723c6050..8b31092a9 100644
--- a/share/qbs/modules/qbs/common.qbs
+++ b/share/qbs/modules/qbs/common.qbs
@@ -102,7 +102,7 @@ Module {
property path installSourceBase
property string installRoot: project.buildDirectory + "/install-root"
property string installDir
- property string installPrefix: qbs.targetOS.includes("unix") ? "/usr/local" : ""
+ property string installPrefix: targetOS.includes("unix") ? "/usr/local" : ""
property path sysroot
PropertyOptions {
diff --git a/src/app/config/configcommandlineparser.cpp b/src/app/config/configcommandlineparser.cpp
index 173676d6d..1ad0cc4b1 100644
--- a/src/app/config/configcommandlineparser.cpp
+++ b/src/app/config/configcommandlineparser.cpp
@@ -126,7 +126,7 @@ void ConfigCommandLineParser::parse(const QStringList &commandLine)
throw Error(Tr::tr("Profile properties must be provided."));
if (m_command.varNames.size() % 2 != 0)
throw Error(Tr::tr("Profile properties must be key/value pairs."));
- for (const auto &varName : qAsConst(m_command.varNames)) {
+ for (const auto &varName : std::as_const(m_command.varNames)) {
if (varName.isEmpty())
throw Error(Tr::tr("Property names must not be empty."));
}
diff --git a/src/app/qbs-create-project/createproject.cpp b/src/app/qbs-create-project/createproject.cpp
index 4167a396e..22c845994 100644
--- a/src/app/qbs-create-project/createproject.cpp
+++ b/src/app/qbs-create-project/createproject.cpp
@@ -124,7 +124,7 @@ void ProjectCreator::serializeProject(const ProjectCreator::Project &project)
fileContents << indent << "Depends { name: \"cpp\" }\n";
}
fileContents << indent << "files: [\n";
- for (const QString &fileName : qAsConst(project.fileNames))
+ for (const QString &fileName : std::as_const(project.fileNames))
fileContents << indent << indent << qbs::toJSLiteral(fileName) << ",\n";
fileContents << indent << "]\n";
for (const ProjectPtr &p : project.subProjects)
@@ -152,7 +152,7 @@ void ProjectCreator::addGroups(QTextStream &stream, const QDir &baseDir,
<< qbs::toJSLiteral(baseDir.relativeFilePath(subProject.dirPath) + QLatin1Char('/'))
<< '\n';
stream << indent << indent << "files: [\n";
- for (const QString &fileName : qAsConst(subProject.fileNames))
+ for (const QString &fileName : std::as_const(subProject.fileNames))
stream << indent << indent << indent << qbs::toJSLiteral(fileName) << ",\n";
stream << indent << indent << "]\n";
stream << indent << "}\n";
@@ -184,7 +184,7 @@ ProjectCreator::ProductFlags ProjectCreator::getFlags(const ProjectCreator::Proj
void ProjectCreator::getFlagsFromFileNames(const ProjectCreator::Project &project,
ProductFlags &flags)
{
- for (const QString &fileName : qAsConst(project.fileNames)) {
+ for (const QString &fileName : std::as_const(project.fileNames)) {
if (flags.testFlag(IsApp) && flags.testFlag(NeedsQt))
return;
const QFileInfo fi(project.dirPath + QLatin1Char('/') + fileName);
@@ -210,7 +210,7 @@ void ProjectCreator::getFlagsFromFileNames(const ProjectCreator::Project &projec
void ProjectCreator::getFlagsFromFileContents(const ProjectCreator::Project &project,
ProductFlags &flags)
{
- for (const QString &fileName : qAsConst(project.fileNames)) {
+ for (const QString &fileName : std::as_const(project.fileNames)) {
QFile f (project.dirPath + QLatin1Char('/') + fileName);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "Ignoring failure to read" << f.fileName();
diff --git a/src/app/qbs-setup-android/android-setup.cpp b/src/app/qbs-setup-android/android-setup.cpp
index 329bd0052..fad24d018 100644
--- a/src/app/qbs-setup-android/android-setup.cpp
+++ b/src/app/qbs-setup-android/android-setup.cpp
@@ -153,7 +153,7 @@ static QtInfoPerArch getQtAndroidInfo(const QString &qtSdkDir)
QDirIterator dit(qtSdkDir, nameFilters, QDir::Dirs);
while (dit.hasNext())
qtDirs << dit.next();
- for (const auto &qtDir : qAsConst(qtDirs)) {
+ for (const auto &qtDir : std::as_const(qtDirs)) {
const QtAndroidInfo info = getInfoForQtDir(qtDir);
if (info.isValid()) {
for (const QString &arch: info.archs)
diff --git a/src/app/qbs-setup-toolchains/gccprobe.cpp b/src/app/qbs-setup-toolchains/gccprobe.cpp
index dda8f2274..c8c04b0cc 100644
--- a/src/app/qbs-setup-toolchains/gccprobe.cpp
+++ b/src/app/qbs-setup-toolchains/gccprobe.cpp
@@ -526,7 +526,7 @@ void gccProbe(Settings *settings, std::vector<Profile> &profiles, const QString
std::vector<QFileInfo> candidates;
const auto filters = buildCompilerNameFilters(compilerName);
- for (const auto &searchPath : qAsConst(searchPaths)) {
+ for (const auto &searchPath : std::as_const(searchPaths)) {
const QDir dir(searchPath);
const QStringList fileNames = dir.entryList(
filters, QDir::Files | QDir::Executable);
@@ -574,7 +574,7 @@ void gccProbe(Settings *settings, std::vector<Profile> &profiles, const QString
});
}
- for (const auto &candidate : qAsConst(candidates)) {
+ for (const auto &candidate : std::as_const(candidates)) {
const QString toolchainType = toolchainTypeFromCompilerName(
candidate.baseName());
const QString profileName = buildProfileName(candidate);
diff --git a/src/app/qbs-setup-toolchains/msvcprobe.cpp b/src/app/qbs-setup-toolchains/msvcprobe.cpp
index e189dd164..84d36753e 100644
--- a/src/app/qbs-setup-toolchains/msvcprobe.cpp
+++ b/src/app/qbs-setup-toolchains/msvcprobe.cpp
@@ -140,7 +140,7 @@ void msvcProbe(Settings *settings, std::vector<Profile> &profiles)
}
}
- for (const WinSDK &sdk : qAsConst(winSDKs)) {
+ for (const WinSDK &sdk : std::as_const(winSDKs)) {
qbsInfo() << Tr::tr(" Windows SDK %1 detected:\n"
" installed in %2").arg(sdk.version, sdk.vcInstallPath);
if (sdk.isDefault)
@@ -150,7 +150,7 @@ void msvcProbe(Settings *settings, std::vector<Profile> &profiles)
// 2) Installed MSVCs
std::vector<MSVC> msvcs = MSVC::installedCompilers(ConsoleLogger::instance());
- for (const MSVC &msvc : qAsConst(msvcs)) {
+ for (const MSVC &msvc : std::as_const(msvcs)) {
qbsInfo() << Tr::tr(" MSVC %1 (%2) detected in\n"
" %3").arg(msvc.version, msvc.architecture,
QDir::toNativeSeparators(msvc.binPath));
diff --git a/src/app/qbs-setup-toolchains/xcodeprobe.cpp b/src/app/qbs-setup-toolchains/xcodeprobe.cpp
index 5fbfcc4e6..9be12d3fc 100644
--- a/src/app/qbs-setup-toolchains/xcodeprobe.cpp
+++ b/src/app/qbs-setup-toolchains/xcodeprobe.cpp
@@ -188,7 +188,7 @@ void XcodeProbe::setupDefaultToolchains(const QString &devPath, const QString &x
<< QStringLiteral("appletvsimulator")
<< QStringLiteral("watchos")
<< QStringLiteral("watchsimulator");
- for (const QString &platform : qAsConst(platforms)) {
+ for (const QString &platform : std::as_const(platforms)) {
Profile platformProfile(xcodeName + QLatin1Char('-') + platform, settings);
platformProfile.removeProfile();
platformProfile.setBaseProfile(installationProfile.name());
@@ -212,7 +212,7 @@ void XcodeProbe::detectAll()
{
int i = 1;
detectDeveloperPaths();
- for (const QString &developerPath : qAsConst(developerPaths)) {
+ for (const QString &developerPath : std::as_const(developerPaths)) {
QString profileName = QStringLiteral("xcode");
if (developerPath != defaultDeveloperPath) {
const auto devPath = developerPath.toStdString();
diff --git a/src/app/qbs/commandlinefrontend.cpp b/src/app/qbs/commandlinefrontend.cpp
index c3269ebcf..3ed5cd1ad 100644
--- a/src/app/qbs/commandlinefrontend.cpp
+++ b/src/app/qbs/commandlinefrontend.cpp
@@ -97,9 +97,9 @@ void CommandLineFrontend::checkCancelStatus()
m_cancelTimer->stop();
if (m_resolveJobs.empty() && m_buildJobs.empty())
std::exit(EXIT_FAILURE);
- for (AbstractJob * const job : qAsConst(m_resolveJobs))
+ for (AbstractJob * const job : std::as_const(m_resolveJobs))
job->cancel();
- for (AbstractJob * const job : qAsConst(m_buildJobs))
+ for (AbstractJob * const job : std::as_const(m_buildJobs))
job->cancel();
break;
case CancelStatusCanceling:
@@ -349,7 +349,7 @@ CommandLineFrontend::ProductMap CommandLineFrontend::productsToUse() const
ProductMap products;
QStringList productNames;
const bool useAll = m_parser.products().empty();
- for (const Project &project : qAsConst(m_projects)) {
+ for (const Project &project : std::as_const(m_projects)) {
QList<ProductData> &productList = products[project];
const ProjectData projectData = project.projectData();
for (const ProductData &product : projectData.allProducts()) {
@@ -432,7 +432,7 @@ void CommandLineFrontend::handleProjectsResolved()
void CommandLineFrontend::makeClean()
{
if (m_parser.products().empty()) {
- for (const Project &project : qAsConst(m_projects)) {
+ for (const Project &project : std::as_const(m_projects)) {
m_buildJobs << project.cleanAllProducts(m_parser.cleanOptions(project.profile()), this);
}
} else {
@@ -504,7 +504,7 @@ void CommandLineFrontend::build()
if (m_parser.products().empty()) {
const Project::ProductSelection productSelection = m_parser.withNonDefaultProducts()
? Project::ProductSelectionWithNonDefault : Project::ProductSelectionDefaultOnly;
- for (const Project &project : qAsConst(m_projects))
+ for (const Project &project : std::as_const(m_projects))
m_buildJobs << project.buildAllProducts(buildOptions(project), productSelection, this);
} else {
const ProductMap &products = productsToUse();
@@ -610,7 +610,7 @@ void CommandLineFrontend::listProducts()
void CommandLineFrontend::connectBuildJobs()
{
- for (AbstractJob * const job : qAsConst(m_buildJobs))
+ for (AbstractJob * const job : std::as_const(m_buildJobs))
connectBuildJob(job);
}
@@ -672,7 +672,7 @@ 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 : qAsConst(runnableProducts)) {
+ 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())
diff --git a/src/app/qbs/parser/commandlineoption.cpp b/src/app/qbs/parser/commandlineoption.cpp
index ddbcd4da1..389b304f0 100644
--- a/src/app/qbs/parser/commandlineoption.cpp
+++ b/src/app/qbs/parser/commandlineoption.cpp
@@ -334,7 +334,7 @@ void StringListOption::doParse(const QString &representation, QStringList &input
throw ErrorInfo(Tr::tr("Invalid use of option '%1': Argument list must not be empty.\n"
"Usage: %2").arg(representation, description(command())));
}
- for (const QString &element : qAsConst(m_arguments)) {
+ for (const QString &element : std::as_const(m_arguments)) {
if (element.isEmpty()) {
throw ErrorInfo(Tr::tr("Invalid use of option '%1': Argument list must not contain "
"empty elements.\nUsage: %2")
diff --git a/src/app/qbs/parser/commandlineparser.cpp b/src/app/qbs/parser/commandlineparser.cpp
index c6134ec80..14e26ca42 100644
--- a/src/app/qbs/parser/commandlineparser.cpp
+++ b/src/app/qbs/parser/commandlineparser.cpp
@@ -413,7 +413,7 @@ QString CommandLineParser::CommandLineParserPrivate::generalHelp() const
for (const Command * command : commands)
commandMap.insert(command->representation(), command);
- for (const Command * command : qAsConst(commandMap)) {
+ for (const Command * command : std::as_const(commandMap)) {
help.append(QLatin1String(" ")).append(command->representation());
const QString whitespace
= QString(rhsIndentation - 2 - command->representation().size(), QLatin1Char(' '));
@@ -424,7 +424,7 @@ QString CommandLineParser::CommandLineParserPrivate::generalHelp() const
toolNames.sort();
if (!toolNames.empty()) {
help.append(QLatin1Char('\n')).append(Tr::tr("Auxiliary commands:\n"));
- for (const QString &toolName : qAsConst(toolNames)) {
+ for (const QString &toolName : std::as_const(toolNames)) {
help.append(QLatin1String(" ")).append(toolName);
const QString whitespace = QString(rhsIndentation - 2 - toolName.size(),
QLatin1Char(' '));
@@ -507,7 +507,7 @@ void CommandLineParser::CommandLineParserPrivate::setupBuildConfigurations()
const QVariantMap globalProperties = propertiesPerConfiguration.takeFirst().second;
QList<QVariantMap> buildConfigs;
- for (const PropertyListItem &item : qAsConst(propertiesPerConfiguration)) {
+ for (const PropertyListItem &item : std::as_const(propertiesPerConfiguration)) {
QVariantMap properties = item.second;
for (QVariantMap::ConstIterator globalPropIt = globalProperties.constBegin();
globalPropIt != globalProperties.constEnd(); ++globalPropIt) {
diff --git a/src/app/qbs/parser/parsercommand.cpp b/src/app/qbs/parser/parsercommand.cpp
index 8fa67e241..bbb5db3d1 100644
--- a/src/app/qbs/parser/parsercommand.cpp
+++ b/src/app/qbs/parser/parsercommand.cpp
@@ -164,7 +164,7 @@ QString Command::supportedOptionsDescription() const
}
QString s = Tr::tr("The possible options are:\n");
- for (const CommandLineOption *option : qAsConst(optionMap))
+ for (const CommandLineOption *option : std::as_const(optionMap))
s += option->description(type());
return s;
}
diff --git a/src/app/qbs/status.cpp b/src/app/qbs/status.cpp
index 127d26a50..8ee39e46f 100644
--- a/src/app/qbs/status.cpp
+++ b/src/app/qbs/status.cpp
@@ -143,9 +143,8 @@ int printStatus(const ProjectData &project)
qbsInfo() << " Group: " << group.name()
<< " (" << group.location().filePath() << ":"
<< group.location().line() << ")";
- QStringList sourceFiles = group.allFilePaths();
- std::sort(sourceFiles.begin(), sourceFiles.end());
- for (const QString &sourceFile : qAsConst(sourceFiles)) {
+ const QStringList sourceFiles = Internal::sorted(group.allFilePaths());
+ for (const QString &sourceFile : sourceFiles) {
if (!QFileInfo::exists(sourceFile))
missingFiles.push_back(sourceFile);
qbsInfo() << " " << sourceFile.mid(projectDirectoryPathLength + 1);
@@ -155,11 +154,11 @@ int printStatus(const ProjectData &project)
}
qbsInfo() << "\nMissing files:";
- for (const QString &untrackedFile : qAsConst(missingFiles))
+ for (const QString &untrackedFile : std::as_const(missingFiles))
qbsInfo() << " " << untrackedFile.mid(projectDirectoryPathLength + 1);
qbsInfo() << "\nUntracked files:";
- for (const QString &missingFile : qAsConst(untrackedFilesInProject))
+ for (const QString &missingFile : std::as_const(untrackedFilesInProject))
qbsInfo() << " " << missingFile.mid(projectDirectoryPathLength + 1);
return 0;
diff --git a/src/lib/corelib/CMakeLists.txt b/src/lib/corelib/CMakeLists.txt
index ff3be4071..4bf7575c3 100644
--- a/src/lib/corelib/CMakeLists.txt
+++ b/src/lib/corelib/CMakeLists.txt
@@ -183,10 +183,6 @@ list_transform_prepend(JS_EXTENSIONS_MACOS_SOURCES jsextensions/)
set(LANGUAGE_SOURCES
artifactproperties.cpp
artifactproperties.h
- astimportshandler.cpp
- astimportshandler.h
- astpropertiesitemhandler.cpp
- astpropertiesitemhandler.h
asttools.cpp
asttools.h
builtindeclarations.cpp
@@ -209,31 +205,13 @@ set(LANGUAGE_SOURCES
itemobserver.h
itempool.cpp
itempool.h
- itemreader.cpp
- itemreader.h
- itemreaderastvisitor.cpp
- itemreaderastvisitor.h
- itemreadervisitorstate.cpp
- itemreadervisitorstate.h
itemtype.h
jsimports.h
language.cpp
language.h
- loader.cpp
- loader.h
- moduleloader.cpp
- moduleloader.h
- modulemerger.cpp
- modulemerger.h
moduleproviderinfo.h
- moduleproviderloader.cpp
- moduleproviderloader.h
preparescriptobserver.cpp
preparescriptobserver.h
- probesresolver.cpp
- probesresolver.h
- projectresolver.cpp
- projectresolver.h
property.cpp
property.h
propertydeclaration.cpp
@@ -257,6 +235,48 @@ list_transform_prepend(LANGUAGE_SOURCES language/)
set(LANGUAGE_HEADERS language/forward_decls.h)
+set(LOADER_SOURCES
+ astimportshandler.cpp
+ astimportshandler.h
+ astpropertiesitemhandler.cpp
+ astpropertiesitemhandler.h
+ dependenciesresolver.cpp
+ dependenciesresolver.h
+ groupshandler.cpp
+ groupshandler.h
+ itemreader.cpp
+ itemreader.h
+ itemreaderastvisitor.cpp
+ itemreaderastvisitor.h
+ itemreadervisitorstate.cpp
+ itemreadervisitorstate.h
+ loaderutils.cpp
+ loaderutils.h
+ localprofiles.cpp
+ localprofiles.h
+ moduleinstantiator.cpp
+ moduleinstantiator.h
+ moduleloader.cpp
+ moduleloader.h
+ modulepropertymerger.cpp
+ modulepropertymerger.h
+ moduleproviderloader.cpp
+ moduleproviderloader.h
+ probesresolver.cpp
+ probesresolver.h
+ productitemmultiplexer.cpp
+ productitemmultiplexer.h
+ productscollector.cpp
+ productscollector.h
+ productshandler.cpp
+ productshandler.h
+ projectresolver.cpp
+ projectresolver.h
+ projecttreebuilder.cpp
+ projecttreebuilder.h
+ )
+list_transform_prepend(LOADER_SOURCES loader/)
+
set(LOGGING_SOURCES
categories.cpp
categories.h
@@ -331,6 +351,7 @@ set(TOOLS_SOURCES
msvcinfo.cpp
msvcinfo.h
pathutils.h
+ pimpl.h
persistence.cpp
persistence.h
porting.h
@@ -345,6 +366,7 @@ set(TOOLS_SOURCES
progressobserver.cpp
progressobserver.h
projectgeneratormanager.cpp
+ propagate_const.h
qbsassert.cpp
qbsassert.h
qbspluginmanager.cpp
@@ -449,6 +471,7 @@ add_qbs_library(qbscore
${JS_EXTENSIONS_MACOS_SOURCES}
${LANGUAGE_SOURCES}
${LANGUAGE_HEADERS}
+ ${LOADER_SOURCES}
${LOGGING_SOURCES}
${LOGGING_HEADERS}
${PARSER_SOURCES}
diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp
index 6c2987cd2..5e810f0b6 100644
--- a/src/lib/corelib/api/internaljobs.cpp
+++ b/src/lib/corelib/api/internaljobs.cpp
@@ -49,8 +49,8 @@
#include <buildgraph/productinstaller.h>
#include <buildgraph/rulesevaluationcontext.h>
#include <language/language.h>
-#include <language/loader.h>
#include <language/scriptengine.h>
+#include <loader/projectresolver.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
@@ -324,16 +324,14 @@ void InternalSetupProjectJob::execute()
void InternalSetupProjectJob::resolveProjectFromScratch(ScriptEngine *engine)
{
- Loader loader(engine, logger());
- loader.setSearchPaths(m_parameters.searchPaths());
- loader.setProgressObserver(observer());
- m_newProject = loader.loadProject(m_parameters);
+ ProjectResolver resolver(engine, logger());
+ resolver.setProgressObserver(observer());
+ m_newProject = resolver.resolve(m_parameters);
QBS_CHECK(m_newProject);
}
void InternalSetupProjectJob::resolveBuildDataFromScratch(const RulesEvaluationContextPtr &evalContext)
{
- TimedActivityLogger resolveLogger(logger(), QStringLiteral("Resolving build project"), timed());
BuildDataResolver(logger()).resolveBuildData(m_newProject, evalContext);
}
diff --git a/src/lib/corelib/api/languageinfo.cpp b/src/lib/corelib/api/languageinfo.cpp
index 505012e88..f20a68ac3 100644
--- a/src/lib/corelib/api/languageinfo.cpp
+++ b/src/lib/corelib/api/languageinfo.cpp
@@ -61,7 +61,7 @@ std::string LanguageInfo::qmlTypeInfo()
// Individual Components:
auto typeNames = builtins.allTypeNames();
typeNames.sort();
- for (const QString &typeName : qAsConst(typeNames)) {
+ for (const QString &typeName : std::as_const(typeNames)) {
const auto typeNameString = typeName.toStdString();
result.append(" Component {\n");
result.append(" name: \"" + typeNameString + "\"\n");
@@ -76,12 +76,10 @@ std::string LanguageInfo::qmlTypeInfo()
Internal::ItemDeclaration itemDecl
= builtins.declarationsForType(builtins.typeForName(typeName));
- auto properties = itemDecl.properties();
- std::sort(std::begin(properties), std::end(properties), []
- (const Internal::PropertyDeclaration &a, const Internal::PropertyDeclaration &b) {
- return a.name() < b.name();
- });
- for (const Internal::PropertyDeclaration &property : qAsConst(properties)) {
+ const auto properties = Internal::sorted(
+ itemDecl.properties(),
+ [](const auto &lhs, const auto &rhs) { return lhs.name() < rhs.name(); });
+ for (const Internal::PropertyDeclaration &property : properties) {
result.append(" Property { name: \"");
result.append(property.name().toUtf8().data());
result.append("\"; ");
diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp
index 46c73dabd..8d5152a24 100644
--- a/src/lib/corelib/api/project.cpp
+++ b/src/lib/corelib/api/project.cpp
@@ -61,7 +61,6 @@
#include <buildgraph/timestampsupdater.h>
#include <buildgraph/transformer.h>
#include <language/language.h>
-#include <language/projectresolver.h>
#include <language/propertymapinternal.h>
#include <logging/logger.h>
#include <logging/translator.h>
@@ -132,7 +131,7 @@ static void addDependencies(QVector<ResolvedProductPtr> &products)
{
for (int i = 0; i < products.size(); ++i) {
const ResolvedProductPtr &product = products.at(i);
- for (const ResolvedProductPtr &dependency : qAsConst(product->dependencies)) {
+ for (const ResolvedProductPtr &dependency : std::as_const(product->dependencies)) {
if (!products.contains(dependency))
products.push_back(dependency);
}
@@ -192,7 +191,7 @@ static QVector<ResolvedProductPtr> enabledInternalProducts(const ResolvedProject
if (p->enabled && (includingNonDefault || p->builtByDefault()))
products.push_back(p);
}
- for (const auto &subProject : qAsConst(project->subProjects))
+ for (const auto &subProject : std::as_const(project->subProjects))
products << enabledInternalProducts(subProject, includingNonDefault);
return products;
}
@@ -216,7 +215,7 @@ static ResolvedProductPtr internalProductForProject(const ResolvedProjectConstPt
if (matches(product, resolvedProduct))
return resolvedProduct;
}
- for (const auto &subProject : qAsConst(project->subProjects)) {
+ for (const auto &subProject : std::as_const(project->subProjects)) {
const ResolvedProductPtr &p = internalProductForProject(subProject, product);
if (p)
return p;
@@ -375,7 +374,7 @@ ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const Product
context.resolvedProducts = internalProducts(context.products);
const QString groupName = group.isValid() ? group.name() : product.name();
- for (const ResolvedProductPtr &p : qAsConst(context.resolvedProducts)) {
+ for (const ResolvedProductPtr &p : std::as_const(context.resolvedProducts)) {
for (const GroupPtr &g : p->groups) {
if (g->name == groupName) {
context.resolvedGroups << g;
@@ -385,7 +384,7 @@ ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const Product
}
if (context.resolvedGroups.empty())
throw ErrorInfo(Tr::tr("Group '%1' does not exist.").arg(groupName));
- for (const ProductData &p : qAsConst(context.products)) {
+ for (const ProductData &p : std::as_const(context.products)) {
const GroupData &g = findGroupData(p, groupName);
QBS_CHECK(p.isValid());
context.groups << g;
@@ -400,7 +399,7 @@ static bool matchesWildcard(const QString &filePath, const GroupConstPtr &group)
{
if (!group->wildcards)
return false;
- for (const QString &pattern : qAsConst(group->wildcards->patterns)) {
+ for (const QString &pattern : std::as_const(group->wildcards->patterns)) {
QString fullPattern;
if (QFileInfo(group->prefix).isAbsolute()) {
fullPattern = group->prefix;
@@ -464,8 +463,8 @@ void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group
// We do not check for entries in other groups, because such doublettes might be legitimate
// due to conditions.
- for (const GroupPtr &group : qAsConst(groupContext.resolvedGroups)) {
- for (const QString &filePath : qAsConst(filesContext.absoluteFilePaths)) {
+ for (const GroupPtr &group : std::as_const(groupContext.resolvedGroups)) {
+ for (const QString &filePath : std::as_const(filesContext.absoluteFilePaths)) {
for (const auto &sa : group->files) {
if (sa->absoluteFilePath == filePath) {
throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.")
@@ -528,7 +527,7 @@ void ProjectPrivate::prepareChangeToProject()
RuleCommandList ProjectPrivate::ruleCommandListForTransformer(const Transformer *transformer)
{
RuleCommandList list;
- for (const AbstractCommandPtr &internalCommand : qAsConst(transformer->commands.commands())) {
+ for (const AbstractCommandPtr &internalCommand : std::as_const(transformer->commands.commands())) {
RuleCommand externalCommand;
externalCommand.d->description = internalCommand->description();
externalCommand.d->extendedDescription = internalCommand->extendedDescription();
@@ -569,11 +568,11 @@ RuleCommandList ProjectPrivate::ruleCommands(const ProductData &product,
QBS_CHECK(resolvedProduct->buildData);
const ArtifactSet &outputArtifacts = resolvedProduct->buildData->artifactsByFileTag()
.value(FileTag(outputFileTag.toLocal8Bit()));
- for (const Artifact * const outputArtifact : qAsConst(outputArtifacts)) {
+ for (const Artifact * const outputArtifact : std::as_const(outputArtifacts)) {
const TransformerConstPtr transformer = outputArtifact->transformer;
if (!transformer)
continue;
- for (const Artifact * const inputArtifact : qAsConst(transformer->inputs)) {
+ for (const Artifact * const inputArtifact : std::as_const(transformer->inputs)) {
if (inputArtifact->filePath() == inputFilePath)
return ruleCommandListForTransformer(transformer.get());
}
@@ -691,7 +690,7 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData,
}
}
for (const ResolvedProductPtr &resolvedDependentProduct
- : qAsConst(resolvedProduct->dependencies)) {
+ : std::as_const(resolvedProduct->dependencies)) {
product.d->dependencies << resolvedDependentProduct->fullDisplayName();
}
std::sort(product.d->type.begin(), product.d->type.end());
@@ -700,7 +699,7 @@ void ProjectPrivate::retrieveProjectData(ProjectData &projectData,
product.d->isValid = true;
projectData.d->products << product;
}
- for (const auto &internalSubProject : qAsConst(internalProject->subProjects)) {
+ for (const auto &internalSubProject : std::as_const(internalProject->subProjects)) {
if (!internalSubProject->enabled)
continue;
ProjectData subProject;
@@ -1006,7 +1005,7 @@ Project::BuildGraphInfo Project::getBuildGraphInfo(const QString &bgFilePath,
const Internal::TopLevelProjectConstPtr project = BuildGraphLoader::loadProject(bgFilePath);
info.bgFilePath = bgFilePath;
info.overriddenProperties = project->overriddenValues;
- info.profileData = project->profileConfigs;
+ info.profileData = project->fullProfileConfigsTree();
std::vector<std::pair<QString, QString>> props;
for (const QString &prop : requestedProperties) {
QStringList components = prop.split(QLatin1Char('.'));
@@ -1045,7 +1044,7 @@ Project::BuildGraphInfo Project::getBuildGraphInfo() const
throw ErrorInfo(Tr::tr("A job is currently in progress."));
info.bgFilePath = d->internalProject->buildGraphFilePath();
info.overriddenProperties = d->internalProject->overriddenValues;
- info.profileData = d->internalProject->profileConfigs;
+ info.profileData = d->internalProject->fullProfileConfigsTree();
} catch (const ErrorInfo &e) {
info.error = e;
}
diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp
index 750186539..9de2d00ee 100644
--- a/src/lib/corelib/api/projectdata.cpp
+++ b/src/lib/corelib/api/projectdata.cpp
@@ -42,6 +42,7 @@
#include "propertymap_p.h"
#include <language/language.h>
#include <language/propertymapinternal.h>
+#include <loader/productitemmultiplexer.h>
#include <tools/fileinfo.h>
#include <tools/jsliterals.h>
#include <tools/qbsassert.h>
@@ -558,7 +559,7 @@ const QString &ProductData::name() const
*/
QString ProductData::fullDisplayName() const
{
- return ResolvedProduct::fullDisplayName(name(), multiplexConfigurationId());
+ return ProductItemMultiplexer::fullProductDisplayName(name(), multiplexConfigurationId());
}
/*!
@@ -635,14 +636,14 @@ const QList<ArtifactData> ProductData::targetArtifacts() const
const QList<ArtifactData> ProductData::installableArtifacts() const
{
QList<ArtifactData> artifacts;
- for (const GroupData &g : qAsConst(d->groups)) {
+ for (const GroupData &g : std::as_const(d->groups)) {
const auto sourceArtifacts = g.allSourceArtifacts();
for (const ArtifactData &a : sourceArtifacts) {
if (a.installData().isInstallable())
artifacts << a;
}
}
- for (const ArtifactData &a : qAsConst(d->generatedArtifacts)) {
+ for (const ArtifactData &a : std::as_const(d->generatedArtifacts)) {
if (a.installData().isInstallable())
artifacts << a;
}
@@ -867,7 +868,7 @@ const QList<ProjectData> &ProjectData::subProjects() const
const QList<ProductData> ProjectData::allProducts() const
{
QList<ProductData> productList = products();
- for (const ProjectData &pd : qAsConst(d->subProjects))
+ for (const ProjectData &pd : std::as_const(d->subProjects))
productList << pd.allProducts();
return productList;
}
@@ -987,11 +988,10 @@ QVariant PropertyMap::getModuleProperty(const QString &moduleName,
static QString mapToString(const QVariantMap &map, const QString &prefix)
{
- QStringList keys(map.keys());
- std::sort(keys.begin(), keys.end());
QString stringRep;
- for (const QString &key : qAsConst(keys)) {
- const QVariant &val = map.value(key);
+ for (auto it = map.cbegin(), end = map.cend(); it != end; ++it) {
+ const QString &key = it.key();
+ const QVariant &val = it.value();
if (val.userType() == QMetaType::QVariantMap) {
stringRep += mapToString(val.value<QVariantMap>(), prefix + key + QLatin1Char('.'));
} else {
diff --git a/src/lib/corelib/api/projectfileupdater.cpp b/src/lib/corelib/api/projectfileupdater.cpp
index b31cdf7a3..5b9f9bced 100644
--- a/src/lib/corelib/api/projectfileupdater.cpp
+++ b/src/lib/corelib/api/projectfileupdater.cpp
@@ -182,7 +182,7 @@ void ProjectFileUpdater::apply()
if (!parserMessages.empty()) {
ErrorInfo errorInfo;
errorInfo.append(Tr::tr("Failure parsing project file."));
- for (const DiagnosticMessage &msg : qAsConst(parserMessages))
+ for (const DiagnosticMessage &msg : std::as_const(parserMessages))
errorInfo.append(msg.message, toCodeLocation(file.fileName(), msg.loc));
throw errorInfo;
}
@@ -480,7 +480,7 @@ void ProjectFileFilesRemover::doApply(QString &fileContent, UiProgram *ast)
}
QString filesString;
filesString += QLatin1String("[\n");
- for (const QString &file : qAsConst(newFilesList)) {
+ for (const QString &file : std::as_const(newFilesList)) {
filesString += QString(arrayElemIndentation, QLatin1Char(' '));
filesString += QStringLiteral("\"%1\",\n").arg(file);
}
diff --git a/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp b/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp
index 6a470bc17..6d0ea738f 100644
--- a/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp
+++ b/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp
@@ -135,7 +135,7 @@ static int getArtifactsPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint
if (!tags.isEmpty()) {
*ptab = reinterpret_cast<JSPropertyEnum *>(js_malloc(ctx, *plen * sizeof **ptab));
JSPropertyEnum *entry = *ptab;
- for (const QString &tag : qAsConst(tags)) {
+ for (const QString &tag : std::as_const(tags)) {
entry->atom = JS_NewAtom(ctx, tag.toUtf8().constData());
entry->is_enumerable = 1;
++entry;
diff --git a/src/lib/corelib/buildgraph/artifactvisitor.cpp b/src/lib/corelib/buildgraph/artifactvisitor.cpp
index 22c987572..885292f06 100644
--- a/src/lib/corelib/buildgraph/artifactvisitor.cpp
+++ b/src/lib/corelib/buildgraph/artifactvisitor.cpp
@@ -55,7 +55,7 @@ void ArtifactVisitor::visitProduct(const ResolvedProductConstPtr &product)
{
if (!product->buildData)
return;
- for (BuildGraphNode *node : qAsConst(product->buildData->allNodes()))
+ for (BuildGraphNode *node : std::as_const(product->buildData->allNodes()))
node->accept(this);
}
diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp
index df3301d57..d641627e7 100644
--- a/src/lib/corelib/buildgraph/buildgraph.cpp
+++ b/src/lib/corelib/buildgraph/buildgraph.cpp
@@ -283,7 +283,7 @@ private:
} else {
productDeps = product->dependencies;
}
- for (const ResolvedProductPtr &dependency : qAsConst(productDeps)) {
+ for (const ResolvedProductPtr &dependency : std::as_const(productDeps)) {
setupBaseProductScriptValue(engine, dependency.get());
JSValue obj = JS_NewObjectClass(engine->context(),
engine->productPropertyScriptClass());
@@ -520,7 +520,7 @@ bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList<BuildGraphNode *> &pat
return true;
}
- for (BuildGraphNode * const childNode : qAsConst(u->children)) {
+ for (BuildGraphNode * const childNode : std::as_const(u->children)) {
if (findPath(childNode, v, path)) {
path.prepend(u);
return true;
@@ -809,17 +809,17 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
QBS_CHECK(buildData);
if (!product->buildData)
return;
- for (BuildGraphNode * const node : qAsConst(buildData->rootNodes())) {
+ for (BuildGraphNode * const node : std::as_const(buildData->rootNodes())) {
qCDebug(lcBuildGraph).noquote() << "Checking root node" << node->toString();
QBS_CHECK(buildData->allNodes().contains(node));
}
Set<QString> filePaths;
- for (BuildGraphNode * const node : qAsConst(buildData->allNodes())) {
+ for (BuildGraphNode * const node : std::as_const(buildData->allNodes())) {
qCDebug(lcBuildGraph).noquote() << "Sanity checking node" << node->toString();
QBS_CHECK(node->product == product);
- for (const BuildGraphNode * const parent : qAsConst(node->parents))
+ for (const BuildGraphNode * const parent : std::as_const(node->parents))
QBS_CHECK(parent->children.contains(node));
- for (BuildGraphNode * const child : qAsConst(node->children)) {
+ for (BuildGraphNode * const child : std::as_const(node->children)) {
QBS_CHECK(child->parents.contains(node));
QBS_CHECK(!child->product.expired());
QBS_CHECK(child->product->buildData);
@@ -846,7 +846,7 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
!filePaths.contains(artifact->filePath()));
filePaths << artifact->filePath();
- for (Artifact * const child : qAsConst(artifact->childrenAddedByScanner))
+ for (Artifact * const child : std::as_const(artifact->childrenAddedByScanner))
QBS_CHECK(artifact->children.contains(child));
const TransformerConstPtr transformer = artifact->transformer;
if (artifact->artifactType == Artifact::SourceFile)
@@ -865,7 +865,7 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
qCDebug(lcBuildGraph)
<< "The transformer has" << transformer->outputs.size() << "outputs.";
ArtifactSet transformerOutputChildren;
- for (const Artifact * const output : qAsConst(transformer->outputs)) {
+ for (const Artifact * const output : std::as_const(transformer->outputs)) {
QBS_CHECK(output->transformer == transformer);
transformerOutputChildren.unite(ArtifactSet::filtered(output->children));
for (const Artifact *a : filterByType<Artifact>(output->children)) {
@@ -883,14 +883,14 @@ static void doSanityChecksForProduct(const ResolvedProductConstPtr &product,
}
if (lcBuildGraph().isDebugEnabled()) {
qCDebug(lcBuildGraph) << "The transformer output children are:";
- for (const Artifact * const a : qAsConst(transformerOutputChildren))
+ for (const Artifact * const a : std::as_const(transformerOutputChildren))
qCDebug(lcBuildGraph) << "\t" << a->fileName();
qCDebug(lcBuildGraph) << "The transformer inputs are:";
- for (const Artifact * const a : qAsConst(transformer->inputs))
+ for (const Artifact * const a : std::as_const(transformer->inputs))
qCDebug(lcBuildGraph) << "\t" << a->fileName();
}
QBS_CHECK(transformer->inputs.size() <= transformerOutputChildren.size());
- for (Artifact * const transformerInput : qAsConst(transformer->inputs))
+ for (Artifact * const transformerInput : std::as_const(transformer->inputs))
QBS_CHECK(transformerOutputChildren.contains(transformerInput));
transformer->artifactsMapRequestedInPrepareScript.doSanityChecks();
transformer->artifactsMapRequestedInCommands.doSanityChecks();
@@ -902,7 +902,7 @@ static void doSanityChecks(const ResolvedProjectPtr &project,
const Logger &logger)
{
logger.qbsDebug() << "Sanity checking project '" << project->name << "'";
- for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects))
+ for (const ResolvedProjectPtr &subProject : std::as_const(project->subProjects))
doSanityChecks(subProject, allProducts, productNames, logger);
for (const auto &product : project->products) {
diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp
index 9d65f42e4..fc05d292c 100644
--- a/src/lib/corelib/buildgraph/buildgraphloader.cpp
+++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp
@@ -39,21 +39,20 @@
#include "buildgraphloader.h"
#include "buildgraph.h"
-#include "cycledetector.h"
#include "emptydirectoriesremover.h"
#include "productbuilddata.h"
#include "projectbuilddata.h"
#include "rulenode.h"
#include "rulecommands.h"
-#include "rulesevaluationcontext.h"
#include "transformer.h"
+#include <buildgraph/rulesevaluationcontext.h>
#include <language/artifactproperties.h>
#include <language/language.h>
-#include <language/loader.h>
#include <language/propertymapinternal.h>
#include <language/qualifiedid.h>
#include <language/resolvedfilecontext.h>
+#include <loader/projectresolver.h>
#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
@@ -95,7 +94,7 @@ static void restoreBackPointers(const ResolvedProjectPtr &project)
product->project = project;
if (!product->buildData)
continue;
- for (BuildGraphNode * const n : qAsConst(product->buildData->allNodes())) {
+ for (BuildGraphNode * const n : std::as_const(product->buildData->allNodes())) {
if (n->type() == BuildGraphNode::ArtifactNodeType) {
project->topLevelProject()->buildData
->insertIntoLookupTable(static_cast<Artifact *>(n));
@@ -103,7 +102,7 @@ static void restoreBackPointers(const ResolvedProjectPtr &project)
}
}
- for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects)) {
+ for (const ResolvedProjectPtr &subProject : std::as_const(project->subProjects)) {
subProject->parentProject = project;
restoreBackPointers(subProject);
}
@@ -129,7 +128,7 @@ BuildGraphLoadResult BuildGraphLoader::load(const TopLevelProjectPtr &existingPr
if (!m_result.loadedProject)
return m_result;
if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly) {
- for (const ErrorInfo &e : qAsConst(m_result.loadedProject->warningsEncountered))
+ for (const ErrorInfo &e : std::as_const(m_result.loadedProject->warningsEncountered))
m_logger.printWarning(e);
return m_result;
}
@@ -220,7 +219,7 @@ bool BuildGraphLoader::checkBuildGraphCompatibility(const TopLevelProjectConstPt
if (m_parameters.projectFilePath().isEmpty())
m_parameters.setProjectFilePath(project->location.filePath());
else
- Loader::setupProjectFilePath(m_parameters);
+ m_parameters.finalizeProjectFilePath();
if (QFileInfo(project->location.filePath()) == QFileInfo(m_parameters.projectFilePath()))
return true;
QString message = Tr::tr("Stored build graph at '%1' is for project file '%2', but "
@@ -244,7 +243,7 @@ static bool checkProductForChangedDependency(std::vector<ResolvedProductPtr> &ch
return false;
if (contains(changedProducts, product))
return true;
- for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) {
+ for (const ResolvedProductPtr &dep : std::as_const(product->dependencies)) {
if (checkProductForChangedDependency(changedProducts, seenProducts, dep)) {
changedProducts << product;
return true;
@@ -269,14 +268,14 @@ static void makeChangedProductsListComplete(std::vector<ResolvedProductPtr> &cha
static void updateProductAndRulePointers(const ResolvedProductPtr &newProduct)
{
std::unordered_map<RuleConstPtr, RuleConstPtr> ruleMap;
- for (BuildGraphNode *node : qAsConst(newProduct->buildData->allNodes())) {
+ for (BuildGraphNode *node : std::as_const(newProduct->buildData->allNodes())) {
node->product = newProduct;
const auto findNewRule = [&ruleMap, &newProduct]
(const RuleConstPtr &oldRule) -> RuleConstPtr {
const auto it = ruleMap.find(oldRule);
if (it != ruleMap.cend())
return it->second;
- for (const auto &r : qAsConst(newProduct->rules)) {
+ for (const auto &r : std::as_const(newProduct->rules)) {
if (*r == *oldRule) {
ruleMap.insert(std::make_pair(oldRule, r));
return r;
@@ -330,7 +329,7 @@ void BuildGraphLoader::trackProjectChanges()
}
if (!reResolvingNecessary) {
- for (const ErrorInfo &e : qAsConst(restoredProject->warningsEncountered))
+ for (const ErrorInfo &e : std::as_const(restoredProject->warningsEncountered))
m_logger.printWarning(e);
return;
}
@@ -339,24 +338,23 @@ void BuildGraphLoader::trackProjectChanges()
markTransformersForChangeTracking(allRestoredProducts);
if (!m_parameters.overrideBuildGraphData())
m_parameters.setEnvironment(restoredProject->environment);
- Loader ldr(m_evalContext->engine(), m_logger);
- ldr.setSearchPaths(m_parameters.searchPaths());
- ldr.setProgressObserver(m_evalContext->observer());
- ldr.setOldProjectProbes(restoredProject->probes);
+ ProjectResolver resolver(m_evalContext->engine(), m_logger);
+ resolver.setProgressObserver(m_evalContext->observer());
+ resolver.setOldProjectProbes(restoredProject->probes);
if (!m_parameters.forceProbeExecution())
- ldr.setStoredModuleProviderInfo(restoredProject->moduleProviderInfo);
- ldr.setLastResolveTime(restoredProject->lastStartResolveTime);
+ resolver.setStoredModuleProviderInfo(restoredProject->moduleProviderInfo);
+ resolver.setLastResolveTime(restoredProject->lastStartResolveTime);
QHash<QString, std::vector<ProbeConstPtr>> restoredProbes;
- for (const auto &restoredProduct : qAsConst(allRestoredProducts))
+ for (const auto &restoredProduct : std::as_const(allRestoredProducts))
restoredProbes.insert(restoredProduct->uniqueName(), restoredProduct->probes);
- ldr.setOldProductProbes(restoredProbes);
+ resolver.setOldProductProbes(restoredProbes);
if (!m_parameters.overrideBuildGraphData())
- ldr.setStoredProfiles(restoredProject->profileConfigs);
- m_result.newlyResolvedProject = ldr.loadProject(m_parameters);
+ resolver.setStoredProfiles(restoredProject->profileConfigs);
+ m_result.newlyResolvedProject = resolver.resolve(m_parameters);
std::vector<ResolvedProductPtr> allNewlyResolvedProducts
= m_result.newlyResolvedProject->allProducts();
- for (const ResolvedProductPtr &cp : qAsConst(allNewlyResolvedProducts))
+ for (const ResolvedProductPtr &cp : std::as_const(allNewlyResolvedProducts))
m_freshProductsByName.insert(cp->uniqueName(), cp);
checkAllProductsForChanges(allRestoredProducts, changedProducts);
@@ -365,7 +363,7 @@ void BuildGraphLoader::trackProjectChanges()
ChildListHash childLists;
if (!changedProducts.empty()) {
oldBuildData = std::make_shared<ProjectBuildData>(restoredProject->buildData.get());
- for (const auto &product : qAsConst(allRestoredProducts)) {
+ for (const auto &product : std::as_const(allRestoredProducts)) {
if (!product->buildData)
continue;
@@ -384,7 +382,7 @@ void BuildGraphLoader::trackProjectChanges()
// mean that artifacts will have to get rebuilt; whether this is necesessary will be decided
// an a per-artifact basis by the Executor on the next build.
QHash<QString, AllRescuableArtifactData> rescuableArtifactData;
- for (const ResolvedProductPtr &product : qAsConst(changedProducts)) {
+ for (const ResolvedProductPtr &product : std::as_const(changedProducts)) {
const QString name = product->uniqueName();
m_changedSourcesByProduct.erase(name);
m_productsWhoseArtifactsNeedUpdate.remove(name);
@@ -428,7 +426,7 @@ void BuildGraphLoader::trackProjectChanges()
}
// Products still left in the list do not exist anymore.
- for (const ResolvedProductPtr &removedProduct : qAsConst(allRestoredProducts)) {
+ for (const ResolvedProductPtr &removedProduct : std::as_const(allRestoredProducts)) {
removeOne(changedProducts, removedProduct);
onProductRemoved(removedProduct, m_result.newlyResolvedProject->buildData.get());
}
@@ -454,7 +452,7 @@ void BuildGraphLoader::trackProjectChanges()
updateGeneratedArtifacts(product.get());
}
- for (const auto &changedProduct : qAsConst(changedProducts)) {
+ for (const auto &changedProduct : std::as_const(changedProducts)) {
rescueOldBuildData(changedProduct,
m_freshProductsByName.value(changedProduct->uniqueName()),
childLists, rescuableArtifactData.value(changedProduct->uniqueName()));
@@ -463,7 +461,7 @@ void BuildGraphLoader::trackProjectChanges()
EmptyDirectoriesRemover(m_result.newlyResolvedProject.get(), m_logger)
.removeEmptyParentDirectories(m_artifactsRemovedFromDisk);
- for (FileResourceBase * const f : qAsConst(m_objectsToDelete)) {
+ for (FileResourceBase * const f : std::as_const(m_objectsToDelete)) {
if (f->fileType() == FileResourceBase::FileTypeArtifact)
static_cast<Artifact *>(f)->product.reset(); // To help with the sanity checks.
}
@@ -583,7 +581,7 @@ bool BuildGraphLoader::hasProductFileChanged(const std::vector<ResolvedProductPt
hasChanged = true;
} else if (!contains(changedProducts, product)) {
bool foundMissingSourceFile = false;
- for (const QString &file : qAsConst(product->missingSourceFiles)) {
+ for (const QString &file : std::as_const(product->missingSourceFiles)) {
if (FileInfo(file).exists()) {
qCDebug(lcBuildGraph) << "Formerly missing file" << file << "in product"
<< product->name << "exists now, must re-resolve project";
@@ -739,9 +737,9 @@ static bool dependenciesAreEqual(const ResolvedProductConstPtr &p1,
return false;
Set<QString> names1;
Set<QString> names2;
- for (const auto &dep : qAsConst(p1->dependencies))
+ for (const auto &dep : std::as_const(p1->dependencies))
names1 << dep->uniqueName();
- for (const auto &dep : qAsConst(p2->dependencies))
+ for (const auto &dep : std::as_const(p2->dependencies))
names2 << dep->uniqueName();
return names1 == names2;
}
@@ -820,7 +818,7 @@ void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product,
removeOne(product->project->products, product);
if (product->buildData) {
- for (BuildGraphNode * const node : qAsConst(product->buildData->allNodes())) {
+ for (BuildGraphNode * const node : std::as_const(product->buildData->allNodes())) {
if (node->type() == BuildGraphNode::ArtifactNodeType) {
const auto artifact = static_cast<Artifact *>(node);
projectBuildData->removeArtifact(artifact, m_logger, removeArtifactsFromDisk,
@@ -828,10 +826,10 @@ void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product,
if (removeArtifactsFromDisk && artifact->artifactType == Artifact::Generated)
m_artifactsRemovedFromDisk << artifact->filePath();
} else {
- for (BuildGraphNode * const parent : qAsConst(node->parents))
+ for (BuildGraphNode * const parent : std::as_const(node->parents))
parent->children.remove(node);
node->parents.clear();
- for (BuildGraphNode * const child : qAsConst(node->children))
+ for (BuildGraphNode * const child : std::as_const(node->children))
child->parents.remove(node);
node->children.clear();
}
@@ -900,8 +898,8 @@ bool BuildGraphLoader::checkConfigCompatibility()
if (m_parameters.finalBuildConfigurationTree() != restoredProject->buildConfiguration())
return false;
Settings settings(m_parameters.settingsDirectory());
- for (QVariantMap::ConstIterator it = restoredProject->profileConfigs.constBegin();
- it != restoredProject->profileConfigs.constEnd(); ++it) {
+ const QVariantMap profileConfigsTree = restoredProject->fullProfileConfigsTree();
+ for (auto it = profileConfigsTree.begin(); it != profileConfigsTree.end(); ++it) {
const Profile profile(it.key(), &settings);
const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration(
profile, m_parameters.configurationName());
@@ -967,7 +965,7 @@ void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restore
rad.lastPrepareScriptExecutionTime
= oldArtifact->transformer->lastPrepareScriptExecutionTime;
const ChildrenInfo &childrenInfo = childLists.value(oldArtifact);
- for (Artifact * const child : qAsConst(childrenInfo.children)) {
+ for (Artifact * const child : std::as_const(childrenInfo.children)) {
rad.children.emplace_back(child->product->name,
child->product->multiplexConfigurationId, child->filePath(),
childrenInfo.childrenAddedByScanner.contains(child));
diff --git a/src/lib/corelib/buildgraph/buildgraphnode.cpp b/src/lib/corelib/buildgraph/buildgraphnode.cpp
index 7d011d50c..cb19ee95c 100644
--- a/src/lib/corelib/buildgraph/buildgraphnode.cpp
+++ b/src/lib/corelib/buildgraph/buildgraphnode.cpp
@@ -56,9 +56,9 @@ BuildGraphNode::BuildGraphNode() : buildState(Untouched)
BuildGraphNode::~BuildGraphNode()
{
- for (BuildGraphNode *p : qAsConst(parents))
+ for (BuildGraphNode *p : std::as_const(parents))
p->children.remove(this);
- for (BuildGraphNode *c : qAsConst(children))
+ for (BuildGraphNode *c : std::as_const(children))
c->parents.remove(this);
}
@@ -69,7 +69,7 @@ void BuildGraphNode::onChildDisconnected(BuildGraphNode *child)
void BuildGraphNode::acceptChildren(BuildGraphVisitor *visitor)
{
- for (BuildGraphNode *child : qAsConst(children))
+ for (BuildGraphNode *child : std::as_const(children))
child->accept(visitor);
}
diff --git a/src/lib/corelib/buildgraph/cycledetector.cpp b/src/lib/corelib/buildgraph/cycledetector.cpp
index 5daed55fd..3a1c43cd1 100644
--- a/src/lib/corelib/buildgraph/cycledetector.cpp
+++ b/src/lib/corelib/buildgraph/cycledetector.cpp
@@ -91,7 +91,7 @@ bool CycleDetector::visitNode(BuildGraphNode *node)
m_nodesInCurrentPath += node;
m_parent = node;
- for (BuildGraphNode * const child : qAsConst(node->children))
+ for (BuildGraphNode * const child : std::as_const(node->children))
child->accept(this);
m_nodesInCurrentPath -= node;
m_allNodes += node;
diff --git a/src/lib/corelib/buildgraph/environmentscriptrunner.cpp b/src/lib/corelib/buildgraph/environmentscriptrunner.cpp
index c4810f621..0fbb3ab19 100644
--- a/src/lib/corelib/buildgraph/environmentscriptrunner.cpp
+++ b/src/lib/corelib/buildgraph/environmentscriptrunner.cpp
@@ -137,7 +137,7 @@ void EnvironmentScriptRunner::setupEnvironment()
QHash<const ResolvedModule*, QList<const ResolvedModule*> > moduleParents;
QHash<const ResolvedModule*, QList<const ResolvedModule*> > moduleChildren;
for (const auto &module : m_product->modules) {
- for (const QString &moduleName : qAsConst(module->moduleDependencies)) {
+ for (const QString &moduleName : std::as_const(module->moduleDependencies)) {
const ResolvedModule * const depmod = moduleMap.value(moduleName);
QBS_ASSERT(depmod, return);
moduleParents[depmod].push_back(module.get());
diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp
index 72ee9d7ba..869d7552a 100644
--- a/src/lib/corelib/buildgraph/executor.cpp
+++ b/src/lib/corelib/buildgraph/executor.cpp
@@ -204,7 +204,7 @@ private:
{
if (!m_seenProducts.insert(product).second)
return;
- for (const ResolvedProductPtr &dependency : qAsConst(product->dependencies))
+ for (const ResolvedProductPtr &dependency : std::as_const(product->dependencies))
traverse(dependency);
if (!product->buildData)
return;
@@ -328,7 +328,7 @@ void Executor::updateLeaves(BuildGraphNode *node, NodeSet &seenNodes)
}
bool isLeaf = true;
- for (BuildGraphNode *child : qAsConst(node->children)) {
+ for (BuildGraphNode *child : std::as_const(node->children)) {
if (child->buildState != BuildGraphNode::Built) {
isLeaf = false;
updateLeaves(child, seenNodes);
@@ -451,7 +451,7 @@ bool Executor::isUpToDate(Artifact *artifact) const
return false;
}
- for (FileDependency *fileDependency : qAsConst(artifact->fileDependencies)) {
+ for (FileDependency *fileDependency : std::as_const(artifact->fileDependencies)) {
if (!fileDependency->timestamp().isValid()) {
qCDebug(lcUpToDateCheck) << "file dependency doesn't exist"
<< fileDependency->filePath();
@@ -478,7 +478,7 @@ bool Executor::mustExecuteTransformer(const TransformerPtr &transformer) const
bool hasAlwaysUpdatedArtifacts = false;
bool hasUpToDateNotAlwaysUpdatedArtifacts = false;
- for (Artifact *artifact : qAsConst(transformer->outputs)) {
+ for (Artifact *artifact : std::as_const(transformer->outputs)) {
if (isUpToDate(artifact)) {
if (artifact->alwaysUpdated)
hasAlwaysUpdatedArtifacts = true;
@@ -567,7 +567,7 @@ void Executor::finishJob(ExecutorJob *job, bool success)
updateJobCounts(transformer.get(), -1);
if (success) {
m_project->buildData->setDirty();
- for (Artifact * const artifact : qAsConst(transformer->outputs)) {
+ for (Artifact * const artifact : std::as_const(transformer->outputs)) {
if (artifact->alwaysUpdated) {
artifact->setTimestamp(FileTime::currentTime());
for (Artifact * const parent : artifact->parentArtifacts())
@@ -626,7 +626,7 @@ static bool allChildrenBuilt(BuildGraphNode *node)
void Executor::finishNode(BuildGraphNode *leaf)
{
leaf->buildState = BuildGraphNode::Built;
- for (BuildGraphNode * const parent : qAsConst(leaf->parents)) {
+ for (BuildGraphNode * const parent : std::as_const(leaf->parents)) {
if (parent->buildState != BuildGraphNode::Buildable) {
qCDebug(lcExec).noquote() << "parent" << parent->toString()
<< "build state:" << toString(parent->buildState);
@@ -679,7 +679,7 @@ bool Executor::transformerHasMatchingInputFiles(const TransformerConstPtr &trans
return false;
if (transformer->inputs.empty())
return true;
- for (const Artifact * const input : qAsConst(transformer->inputs)) {
+ for (const Artifact * const input : std::as_const(transformer->inputs)) {
const auto files = m_buildOptions.filesToConsider();
for (const QString &filePath : files) {
if (input->filePath() == filePath
@@ -695,7 +695,7 @@ bool Executor::transformerHasMatchingInputFiles(const TransformerConstPtr &trans
void Executor::setupJobLimits()
{
Settings settings(m_buildOptions.settingsDirectory());
- for (const auto &p : qAsConst(m_productsToBuild)) {
+ for (const auto &p : std::as_const(m_productsToBuild)) {
const Preferences prefs(&settings, p->profile());
const JobLimits &jobLimitsFromSettings = prefs.jobLimits();
JobLimits effectiveJobLimits;
@@ -732,7 +732,7 @@ void Executor::setupProgressObserver()
if (!m_progressObserver)
return;
int totalEffort = 1; // For the effort after the last rule application;
- for (const auto &product : qAsConst(m_productsToBuild)) {
+ for (const auto &product : std::as_const(m_productsToBuild)) {
QBS_CHECK(product->buildData);
const auto filtered = filterByType<RuleNode>(product->buildData->allNodes());
totalEffort += std::distance(filtered.begin(), filtered.end());
@@ -744,7 +744,7 @@ void Executor::doSanityChecks()
{
QBS_CHECK(m_project);
QBS_CHECK(!m_productsToBuild.empty());
- for (const auto &product : qAsConst(m_productsToBuild)) {
+ for (const auto &product : std::as_const(m_productsToBuild)) {
QBS_CHECK(product->buildData);
QBS_CHECK(product->topLevelProject() == m_project.get());
}
@@ -915,7 +915,7 @@ bool Executor::checkForUnbuiltDependencies(Artifact *artifact)
{
bool buildingDependenciesFound = false;
NodeSet unbuiltDependencies;
- for (BuildGraphNode * const dependency : qAsConst(artifact->children)) {
+ for (BuildGraphNode * const dependency : std::as_const(artifact->children)) {
switch (dependency->buildState) {
case BuildGraphNode::Untouched:
case BuildGraphNode::Buildable:
@@ -946,7 +946,7 @@ bool Executor::checkForUnbuiltDependencies(Artifact *artifact)
void Executor::potentiallyRunTransformer(const TransformerPtr &transformer)
{
- for (Artifact * const output : qAsConst(transformer->outputs)) {
+ for (Artifact * const output : std::as_const(transformer->outputs)) {
// Rescuing build data can introduce new dependencies, potentially delaying execution of
// this transformer.
bool childrenAddedDueToRescue;
@@ -969,7 +969,7 @@ void Executor::potentiallyRunTransformer(const TransformerPtr &transformer)
const bool mustExecute = mustExecuteTransformer(transformer);
if (mustExecute || m_buildOptions.forceTimestampCheck()) {
- for (Artifact * const output : qAsConst(transformer->outputs)) {
+ for (Artifact * const output : std::as_const(transformer->outputs)) {
// Scan all input artifacts. If new dependencies were found during scanning, delay
// execution of this transformer.
InputArtifactScanner scanner(output, m_inputArtifactScanContext, m_logger);
@@ -1000,7 +1000,7 @@ void Executor::runTransformer(const TransformerPtr &transformer)
// create the output directories
if (!m_buildOptions.dryRun()) {
- for (Artifact * const output : qAsConst(transformer->outputs)) {
+ for (Artifact * const output : std::as_const(transformer->outputs)) {
QDir outDir = QFileInfo(output->filePath()).absoluteDir();
if (!outDir.exists() && !outDir.mkpath(StringConstants::dot())) {
throw ErrorInfo(tr("Failed to create directory '%1'.")
@@ -1011,7 +1011,7 @@ void Executor::runTransformer(const TransformerPtr &transformer)
QBS_CHECK(!m_availableJobs.empty());
ExecutorJob *job = m_availableJobs.takeFirst();
- for (Artifact * const artifact : qAsConst(transformer->outputs))
+ for (Artifact * const artifact : std::as_const(transformer->outputs))
artifact->buildState = BuildGraphNode::Building;
m_processingJobs.insert(job, transformer);
updateJobCounts(transformer.get(), 1);
@@ -1021,7 +1021,7 @@ void Executor::runTransformer(const TransformerPtr &transformer)
void Executor::finishTransformer(const TransformerPtr &transformer)
{
transformer->markedForRerun = false;
- for (Artifact * const artifact : qAsConst(transformer->outputs)) {
+ for (Artifact * const artifact : std::as_const(transformer->outputs)) {
possiblyInstallArtifact(artifact);
finishArtifact(artifact);
}
@@ -1082,9 +1082,9 @@ void Executor::checkForUnbuiltProducts()
if (m_buildOptions.executeRulesOnly())
return;
std::vector<ResolvedProductPtr> unbuiltProducts;
- for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) {
+ for (const ResolvedProductPtr &product : std::as_const(m_productsToBuild)) {
bool productBuilt = true;
- for (BuildGraphNode *rootNode : qAsConst(product->buildData->rootNodes())) {
+ for (BuildGraphNode *rootNode : std::as_const(product->buildData->rootNodes())) {
if (rootNode->buildState != BuildGraphNode::Built) {
productBuilt = false;
unbuiltProducts.push_back(product);
@@ -1198,11 +1198,11 @@ void Executor::prepareAllNodes()
for (const ResolvedProductPtr &product : m_allProducts) {
if (product->enabled) {
QBS_CHECK(product->buildData);
- for (BuildGraphNode * const node : qAsConst(product->buildData->allNodes()))
+ for (BuildGraphNode * const node : std::as_const(product->buildData->allNodes()))
node->buildState = BuildGraphNode::Untouched;
}
}
- for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) {
+ for (const ResolvedProductPtr &product : std::as_const(m_productsToBuild)) {
QBS_CHECK(product->buildData);
for (Artifact * const artifact : filterByType<Artifact>(product->buildData->allNodes()))
prepareArtifact(artifact);
@@ -1286,7 +1286,7 @@ void Executor::setupForBuildingSelectedFiles(const BuildGraphNode *node)
*/
void Executor::prepareReachableNodes()
{
- for (BuildGraphNode * const root : qAsConst(m_roots))
+ for (BuildGraphNode * const root : std::as_const(m_roots))
prepareReachableNodes_impl(root);
}
@@ -1298,7 +1298,7 @@ void Executor::prepareReachableNodes_impl(BuildGraphNode *node)
return;
node->buildState = BuildGraphNode::Buildable;
- for (BuildGraphNode *child : qAsConst(node->children))
+ for (BuildGraphNode *child : std::as_const(node->children))
prepareReachableNodes_impl(child);
}
@@ -1306,7 +1306,7 @@ void Executor::prepareProducts()
{
ProductPrioritySetter prioritySetter(m_allProducts);
prioritySetter.apply();
- for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) {
+ for (const ResolvedProductPtr &product : std::as_const(m_productsToBuild)) {
EnvironmentScriptRunner(product.get(), m_evalContext.get(), m_project->environment)
.setupForBuild();
}
@@ -1315,7 +1315,7 @@ void Executor::prepareProducts()
void Executor::setupRootNodes()
{
m_roots.clear();
- for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild))
+ for (const ResolvedProductPtr &product : std::as_const(m_productsToBuild))
m_roots += product->buildData->rootNodes();
}
diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
index 05bbc0d4b..6d0c5cdbd 100644
--- a/src/lib/corelib/buildgraph/inputartifactscanner.cpp
+++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp
@@ -158,7 +158,7 @@ void InputArtifactScanner::scan()
for (Artifact * const dependency : childrenAddedByScanner)
disconnect(m_artifact, dependency);
- for (Artifact * const inputArtifact : qAsConst(m_artifact->transformer->inputs))
+ for (Artifact * const inputArtifact : std::as_const(m_artifact->transformer->inputs))
scanForFileDependencies(inputArtifact);
}
@@ -224,7 +224,7 @@ Set<DependencyScanner *> InputArtifactScanner::scannersForArtifact(const Artifac
}
}
}
- for (const DependencyScannerPtr &scanner : qAsConst(cache.scanners))
+ for (const DependencyScannerPtr &scanner : std::as_const(cache.scanners))
scanners += scanner.get();
}
return scanners;
@@ -243,7 +243,7 @@ void InputArtifactScanner::scanForScannerFileDependencies(DependencyScanner *sca
cache.searchPaths = scanner->collectSearchPaths(inputArtifact);
}
qCDebug(lcDepScan) << "include paths (cache" << (cacheHit ? "hit)" : "miss)");
- for (const QString &s : qAsConst(cache.searchPaths))
+ for (const QString &s : std::as_const(cache.searchPaths))
qCDebug(lcDepScan) << " " << s;
const QString &filePathToBeScanned = fileToBeScanned->filePath();
@@ -289,7 +289,7 @@ void InputArtifactScanner::resolveScanResultDependencies(const Artifact *inputAr
}
// try include paths
- for (const QString &includePath : qAsConst(cache.searchPaths)) {
+ for (const QString &includePath : std::as_const(cache.searchPaths)) {
resolveDepencency(dependency, inputArtifact->product.get(),
&resolvedDependency, includePath);
if (resolvedDependency.isValid())
diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
index 0058b940e..b308e9c47 100644
--- a/src/lib/corelib/buildgraph/processcommandexecutor.cpp
+++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
@@ -398,7 +398,7 @@ void ProcessCommandExecutor::doReportCommandDescription(const QString &productNa
if (m_echoMode == CommandEchoModeCommandLineWithEnvironment) {
QStringList keys = m_commandEnvironment.keys();
keys.sort();
- for (const QString &key : qAsConst(keys))
+ for (const QString &key : std::as_const(keys))
fullInvocation += environmentVariableString(key, m_commandEnvironment.value(key));
}
fullInvocation += m_shellInvocation;
diff --git a/src/lib/corelib/buildgraph/productinstaller.cpp b/src/lib/corelib/buildgraph/productinstaller.cpp
index 80a76d7f5..f757c8a85 100644
--- a/src/lib/corelib/buildgraph/productinstaller.cpp
+++ b/src/lib/corelib/buildgraph/productinstaller.cpp
@@ -96,7 +96,7 @@ void ProductInstaller::install()
removeInstallRoot();
QList<const Artifact *> artifactsToInstall;
- for (const auto &product : qAsConst(m_products)) {
+ for (const auto &product : std::as_const(m_products)) {
QBS_CHECK(product->buildData);
for (const Artifact *artifact : filterByType<Artifact>(product->buildData->allNodes())) {
if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool())
@@ -105,7 +105,7 @@ void ProductInstaller::install()
}
m_observer->initialize(Tr::tr("Installing"), artifactsToInstall.size());
- for (const Artifact * const a : qAsConst(artifactsToInstall)) {
+ for (const Artifact * const a : std::as_const(artifactsToInstall)) {
copyFile(a);
m_observer->incrementProgressValue();
}
diff --git a/src/lib/corelib/buildgraph/projectbuilddata.cpp b/src/lib/corelib/buildgraph/projectbuilddata.cpp
index c229a6171..36ac75331 100644
--- a/src/lib/corelib/buildgraph/projectbuilddata.cpp
+++ b/src/lib/corelib/buildgraph/projectbuilddata.cpp
@@ -157,7 +157,7 @@ void ProjectBuildData::insertFileDependency(FileDependency *dependency)
static void disconnectArtifactChildren(Artifact *artifact)
{
qCDebug(lcBuildGraph) << "disconnect children of" << relativeArtifactFileName(artifact);
- for (BuildGraphNode * const child : qAsConst(artifact->children))
+ for (BuildGraphNode * const child : std::as_const(artifact->children))
child->parents.remove(artifact);
artifact->children.clear();
artifact->childrenAddedByScanner.clear();
@@ -166,7 +166,7 @@ static void disconnectArtifactChildren(Artifact *artifact)
static void disconnectArtifactParents(Artifact *artifact)
{
qCDebug(lcBuildGraph) << "disconnect parents of" << relativeArtifactFileName(artifact);
- for (BuildGraphNode * const parent : qAsConst(artifact->parents)) {
+ for (BuildGraphNode * const parent : std::as_const(artifact->parents)) {
parent->children.remove(artifact);
if (parent->type() != BuildGraphNode::ArtifactNodeType)
continue;
@@ -257,7 +257,7 @@ void ProjectBuildData::setClean()
void ProjectBuildData::load(PersistentPool &pool)
{
serializationOp<PersistentPool::Load>(pool);
- for (FileDependency * const dep : qAsConst(fileDependencies))
+ for (FileDependency * const dep : std::as_const(fileDependencies))
insertIntoLookupTable(dep);
m_isDirty = false;
}
@@ -337,7 +337,7 @@ private:
{
if (!m_rulesOnPath.insert(rule.get()).second) {
QString pathstr;
- for (const Rule *r : qAsConst(m_rulePath)) {
+ for (const Rule *r : std::as_const(m_rulePath)) {
pathstr += QLatin1Char('\n') + r->toString() + QLatin1Char('\t')
+ r->prepareScript.location().toString();
}
@@ -394,7 +394,7 @@ void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &produc
product->buildData = std::make_unique<ProductBuildData>();
ArtifactSetByFileTag artifactsPerFileTag;
- for (const auto &dependency : qAsConst(product->dependencies)) {
+ for (const auto &dependency : std::as_const(product->dependencies)) {
QBS_CHECK(dependency->enabled);
resolveProductBuildData(dependency);
}
diff --git a/src/lib/corelib/buildgraph/rulegraph.cpp b/src/lib/corelib/buildgraph/rulegraph.cpp
index 3f9a4fcb5..2acc2a97e 100644
--- a/src/lib/corelib/buildgraph/rulegraph.cpp
+++ b/src/lib/corelib/buildgraph/rulegraph.cpp
@@ -61,11 +61,11 @@ void RuleGraph::build(const std::vector<RulePtr> &rules, const FileTags &product
m_parents.resize(rules.size());
m_children.resize(rules.size());
- for (const auto &rule : qAsConst(m_rules)) {
+ for (const auto &rule : std::as_const(m_rules)) {
FileTags inFileTags = rule->inputs;
inFileTags += rule->auxiliaryInputs;
inFileTags += rule->explicitlyDependsOn;
- for (const FileTag &fileTag : qAsConst(inFileTags)) {
+ for (const FileTag &fileTag : std::as_const(inFileTags)) {
inputFileTagToRule[fileTag].push_back(rule.get());
for (const Rule * const producingRule : m_outputFileTagToRule.value(fileTag)) {
if (!producingRule->collectedOutputFileTags().intersects(
@@ -82,14 +82,14 @@ void RuleGraph::build(const std::vector<RulePtr> &rules, const FileTags &product
productRules << rules;
//### check: the rule graph must be a in valid shape!
}
- for (const Rule *r : qAsConst(productRules))
+ for (const Rule *r : std::as_const(productRules))
m_rootRules += r->ruleGraphId;
}
void RuleGraph::accept(RuleGraphVisitor *visitor) const
{
const RuleConstPtr nullParent;
- for (int rootIndex : qAsConst(m_rootRules))
+ for (int rootIndex : std::as_const(m_rootRules))
traverse(visitor, nullParent, m_rules.at(rootIndex));
}
@@ -98,10 +98,10 @@ void RuleGraph::dump() const
QByteArray indent;
std::printf("---rule graph dump:\n");
Set<int> rootRules;
- for (const auto &rule : qAsConst(m_rules))
+ for (const auto &rule : std::as_const(m_rules))
if (m_parents[rule->ruleGraphId].empty())
rootRules += rule->ruleGraphId;
- for (int idx : qAsConst(rootRules))
+ for (int idx : std::as_const(rootRules))
dump_impl(indent, idx);
}
@@ -113,7 +113,7 @@ void RuleGraph::dump_impl(QByteArray &indent, int rootIndex) const
std::printf("\n");
indent.append(" ");
- for (int childIndex : qAsConst(m_children[rootIndex]))
+ for (int childIndex : std::as_const(m_children[rootIndex]))
dump_impl(indent, childIndex);
indent.chop(2);
}
diff --git a/src/lib/corelib/buildgraph/rulenode.cpp b/src/lib/corelib/buildgraph/rulenode.cpp
index 0558ba144..8568e4098 100644
--- a/src/lib/corelib/buildgraph/rulenode.cpp
+++ b/src/lib/corelib/buildgraph/rulenode.cpp
@@ -229,7 +229,7 @@ int RuleNode::transformerCount() const
ArtifactSet RuleNode::currentInputArtifacts() const
{
ArtifactSet s;
- for (const FileTag &t : qAsConst(m_rule->inputs)) {
+ for (const FileTag &t : std::as_const(m_rule->inputs)) {
for (Artifact *artifact : product->lookupArtifactsByFileTag(t)) {
if (artifact->isTargetOfModule())
continue;
@@ -246,7 +246,7 @@ ArtifactSet RuleNode::currentInputArtifacts() const
if (m_rule->inputsFromDependencies.empty())
return s;
- for (const FileTag &t : qAsConst(m_rule->inputsFromDependencies)) {
+ for (const FileTag &t : std::as_const(m_rule->inputsFromDependencies)) {
for (Artifact *artifact : product->lookupArtifactsByFileTag(t)) {
if (!artifact->isTargetOfModule())
continue;
@@ -258,7 +258,7 @@ ArtifactSet RuleNode::currentInputArtifacts() const
}
}
- for (const auto &dep : qAsConst(product->dependencies)) {
+ for (const auto &dep : std::as_const(product->dependencies)) {
if (!dep->buildData)
continue;
for (Artifact * const a : filterByType<Artifact>(dep->buildData->allNodes())) {
diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp
index 84956d123..5cc4be96e 100644
--- a/src/lib/corelib/buildgraph/rulesapplicator.cpp
+++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp
@@ -146,7 +146,7 @@ void RulesApplicator::handleRemovedRuleOutputs(const ArtifactSet &inputArtifacts
project->buildData->removeArtifactAndExclusiveDependents(removedArtifact, logger, true,
&artifactsToRemove);
}
- for (Artifact * const artifact : qAsConst(artifactsToRemove)) {
+ for (Artifact * const artifact : std::as_const(artifactsToRemove)) {
QBS_CHECK(!inputArtifacts.contains(artifact));
removedArtifacts << artifact->filePath();
delete artifact;
@@ -247,8 +247,8 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, JSValue prepare
if (outputArtifacts.empty())
return;
- for (Artifact * const outputArtifact : qAsConst(outputArtifacts)) {
- for (Artifact * const dependency : qAsConst(m_transformer->explicitlyDependsOn))
+ for (Artifact * const outputArtifact : std::as_const(outputArtifacts)) {
+ for (Artifact * const dependency : std::as_const(m_transformer->explicitlyDependsOn))
connect(outputArtifact, dependency);
}
@@ -313,7 +313,7 @@ void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, JSValue prepare
|| m_oldTransformer->commands != m_transformer->commands
|| commandsNeedRerun(m_transformer.get(), m_product.get(), m_productsByName,
m_projectsByName)) {
- for (Artifact * const output : qAsConst(outputArtifacts)) {
+ for (Artifact * const output : std::as_const(outputArtifacts)) {
output->clearTimestamp();
m_invalidatedArtifacts += output;
}
diff --git a/src/lib/corelib/buildgraph/transformerchangetracking.cpp b/src/lib/corelib/buildgraph/transformerchangetracking.cpp
index 710590654..f0b8986f4 100644
--- a/src/lib/corelib/buildgraph/transformerchangetracking.cpp
+++ b/src/lib/corelib/buildgraph/transformerchangetracking.cpp
@@ -257,7 +257,7 @@ const ResolvedProduct *TrafoChangeTracker::getProduct(const QString &name) const
bool TrafoChangeTracker::prepareScriptNeedsRerun() const
{
- for (const Property &property : qAsConst(m_transformer->propertiesRequestedInPrepareScript)) {
+ for (const Property &property : std::as_const(m_transformer->propertiesRequestedInPrepareScript)) {
if (checkForPropertyChange(property, propertyMapByKind(property)))
return true;
}
@@ -269,7 +269,7 @@ bool TrafoChangeTracker::prepareScriptNeedsRerun() const
for (auto it = m_transformer->propertiesRequestedFromArtifactInPrepareScript.constBegin();
it != m_transformer->propertiesRequestedFromArtifactInPrepareScript.constEnd(); ++it) {
- for (const Property &property : qAsConst(it.value())) {
+ for (const Property &property : std::as_const(it.value())) {
const Artifact * const artifact = getArtifact(it.key(), property.productName);
if (!artifact)
return true;
@@ -297,14 +297,14 @@ bool TrafoChangeTracker::prepareScriptNeedsRerun() const
bool TrafoChangeTracker::commandsNeedRerun() const
{
- for (const Property &property : qAsConst(m_transformer->propertiesRequestedInCommands)) {
+ for (const Property &property : std::as_const(m_transformer->propertiesRequestedInCommands)) {
if (checkForPropertyChange(property, propertyMapByKind(property)))
return true;
}
for (auto it = m_transformer->propertiesRequestedFromArtifactInCommands.cbegin();
it != m_transformer->propertiesRequestedFromArtifactInCommands.cend(); ++it) {
- for (const Property &property : qAsConst(it.value())) {
+ for (const Property &property : std::as_const(it.value())) {
const Artifact * const artifact = getArtifact(it.key(), property.productName);
if (!artifact)
return true;
@@ -331,7 +331,7 @@ bool TrafoChangeTracker::commandsNeedRerun() const
return true;
// TODO: Also track env access in JS commands and prepare scripts
- for (const AbstractCommandPtr &c : qAsConst(m_transformer->commands.commands())) {
+ for (const AbstractCommandPtr &c : std::as_const(m_transformer->commands.commands())) {
if (c->type() != AbstractCommand::ProcessCommandType)
continue;
const ProcessCommandPtr &processCmd = std::static_pointer_cast<ProcessCommand>(c);
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 1012c1baa..5e837168e 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -14,14 +14,19 @@ QbsLibrary {
".",
"../.." // for the plugin headers
])
- property stringList enableUnitTestsDefines:
- qbsbuildconfig.enableUnitTests ? ["QBS_ENABLE_UNIT_TESTS"] : []
- property stringList systemSettingsDirDefines: qbsbuildconfig.systemSettingsDir
- ? ['QBS_SYSTEM_SETTINGS_DIR="' + qbsbuildconfig.systemSettingsDir + '"'] : []
- cpp.defines: base.concat([
- "QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath),
- "QBS_VERSION=" + Utilities.cStringQuote(version),
- ]).concat(enableUnitTestsDefines).concat(systemSettingsDirDefines)
+ cpp.defines: {
+ var defines = base.concat([
+ "QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath),
+ "QBS_VERSION=" + Utilities.cStringQuote(version),
+ ]);
+ if (project.withTests)
+ defines.push("QBS_WITH_TESTS");
+ if (qbsbuildconfig.enableUnitTests)
+ defines.push("QBS_ENABLE_UNIT_TESTS");
+ if (qbsbuildconfig.systemSettingsDir)
+ defines.push('QBS_SYSTEM_SETTINGS_DIR="' + qbsbuildconfig.systemSettingsDir + '"');
+ return defines;
+ }
Properties {
condition: qbs.targetOS.contains("windows")
@@ -261,10 +266,6 @@ QbsLibrary {
files: [
"artifactproperties.cpp",
"artifactproperties.h",
- "astimportshandler.cpp",
- "astimportshandler.h",
- "astpropertiesitemhandler.cpp",
- "astpropertiesitemhandler.h",
"asttools.cpp",
"asttools.h",
"builtindeclarations.cpp",
@@ -287,31 +288,13 @@ QbsLibrary {
"itemobserver.h",
"itempool.cpp",
"itempool.h",
- "itemreader.cpp",
- "itemreader.h",
- "itemreaderastvisitor.cpp",
- "itemreaderastvisitor.h",
- "itemreadervisitorstate.cpp",
- "itemreadervisitorstate.h",
"itemtype.h",
"jsimports.h",
"language.cpp",
"language.h",
- "loader.cpp",
- "loader.h",
- "moduleloader.cpp",
- "moduleloader.h",
- "modulemerger.cpp",
- "modulemerger.h",
"moduleproviderinfo.h",
- "moduleproviderloader.cpp",
- "moduleproviderloader.h",
"preparescriptobserver.cpp",
"preparescriptobserver.h",
- "probesresolver.cpp",
- "probesresolver.h",
- "projectresolver.cpp",
- "projectresolver.h",
"property.cpp",
"property.h",
"propertydeclaration.cpp",
@@ -339,6 +322,50 @@ QbsLibrary {
files: "language/forward_decls.h"
}
Group {
+ name: "loader"
+ prefix: name + '/'
+ files: [
+ "astimportshandler.cpp",
+ "astimportshandler.h",
+ "astpropertiesitemhandler.cpp",
+ "astpropertiesitemhandler.h",
+ "dependenciesresolver.cpp",
+ "dependenciesresolver.h",
+ "groupshandler.cpp",
+ "groupshandler.h",
+ "itemreader.cpp",
+ "itemreader.h",
+ "itemreaderastvisitor.cpp",
+ "itemreaderastvisitor.h",
+ "itemreadervisitorstate.cpp",
+ "itemreadervisitorstate.h",
+ "loaderutils.cpp",
+ "loaderutils.h",
+ "localprofiles.cpp",
+ "localprofiles.h",
+ "moduleinstantiator.cpp",
+ "moduleinstantiator.h",
+ "moduleloader.cpp",
+ "moduleloader.h",
+ "modulepropertymerger.cpp",
+ "modulepropertymerger.h",
+ "moduleproviderloader.cpp",
+ "moduleproviderloader.h",
+ "probesresolver.cpp",
+ "probesresolver.h",
+ "productitemmultiplexer.cpp",
+ "productitemmultiplexer.h",
+ "productscollector.cpp",
+ "productscollector.h",
+ "productshandler.cpp",
+ "productshandler.h",
+ "projectresolver.cpp",
+ "projectresolver.h",
+ "projecttreebuilder.cpp",
+ "projecttreebuilder.h",
+ ]
+ }
+ Group {
name: "logging"
prefix: name + '/'
files: [
@@ -424,6 +451,7 @@ QbsLibrary {
"msvcinfo.cpp",
"msvcinfo.h",
"pathutils.h",
+ "pimpl.h",
"persistence.cpp",
"persistence.h",
"porting.h",
@@ -438,6 +466,7 @@ QbsLibrary {
"progressobserver.cpp",
"progressobserver.h",
"projectgeneratormanager.cpp",
+ "propagate_const.h",
"qbsassert.cpp",
"qbsassert.h",
"qbspluginmanager.cpp",
diff --git a/src/lib/corelib/generators/generator.cpp b/src/lib/corelib/generators/generator.cpp
index d3cdce412..8fdd3555e 100644
--- a/src/lib/corelib/generators/generator.cpp
+++ b/src/lib/corelib/generators/generator.cpp
@@ -82,14 +82,11 @@ ErrorInfo ProjectGenerator::generate(const QList<Project> &projects,
const QString &qbsSettingsDir,
const Internal::Logger &logger)
{
- d->projects = projects;
- std::sort(d->projects.begin(), d->projects.end(),
- [](const Project &a, const Project &b) {
- return _configurationName(a) < _configurationName(b); });
- d->buildConfigurations = buildConfigurations;
- std::sort(d->buildConfigurations.begin(), d->buildConfigurations.end(),
- [](const QVariantMap &a, const QVariantMap &b) {
- return _configurationName(a) < _configurationName(b); });
+ d->projects = Internal::sorted(projects, [](const Project &lhs, const Project &rhs) {
+ return _configurationName(lhs) < _configurationName(rhs); });
+ d->buildConfigurations = Internal::sorted(
+ buildConfigurations, [](const QVariantMap &lhs, const QVariantMap &rhs) {
+ return _configurationName(lhs) < _configurationName(rhs); });
d->installOptions = installOptions;
d->qbsSettingsDir = qbsSettingsDir;
d->logger = logger;
@@ -210,7 +207,7 @@ const GeneratableProject ProjectGenerator::project() const
{
QMap<QString, ProjectData> rootProjects;
GeneratableProject proj;
- for (const auto &project : qAsConst(d->projects)) {
+ for (const auto &project : std::as_const(d->projects)) {
const QString configurationName = _configurationName(project);
rootProjects.insert(configurationName, project.projectData());
proj.projects.insert(configurationName, project);
diff --git a/src/lib/corelib/generators/generatorutils.cpp b/src/lib/corelib/generators/generatorutils.cpp
index de283755c..d2dcbba25 100644
--- a/src/lib/corelib/generators/generatorutils.cpp
+++ b/src/lib/corelib/generators/generatorutils.cpp
@@ -127,7 +127,7 @@ std::vector<ProductData> dependenciesOf(const ProductData &qbsProduct,
{
std::vector<ProductData> result;
const auto &depsNames = qbsProduct.dependencies();
- for (const auto &product : qAsConst(genProject.products)) {
+ for (const auto &product : std::as_const(genProject.products)) {
const auto pt = product.type();
if (!pt.contains(QLatin1String("staticlibrary")))
continue;
diff --git a/src/lib/corelib/jsextensions/moduleproperties.cpp b/src/lib/corelib/jsextensions/moduleproperties.cpp
index 4f83bf640..2c73f2c53 100644
--- a/src/lib/corelib/jsextensions/moduleproperties.cpp
+++ b/src/lib/corelib/jsextensions/moduleproperties.cpp
@@ -177,7 +177,7 @@ static JSValue js_moduleDependencies(JSContext *ctx, JSValueConst this_val, int
const auto module = attachedPointer<ResolvedModule>(this_val, engine->dataWithPtrClass());
JSValue result = JS_NewArray(engine->context());
quint32 idx = 0;
- for (const QString &depName : qAsConst(module->moduleDependencies)) {
+ for (const QString &depName : std::as_const(module->moduleDependencies)) {
for (const auto &dep : module->product->modules) {
if (dep->name != depName)
continue;
diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp
index 7004244fa..acf50b4f3 100644
--- a/src/lib/corelib/language/builtindeclarations.cpp
+++ b/src/lib/corelib/language/builtindeclarations.cpp
@@ -345,7 +345,11 @@ ItemDeclaration BuiltinDeclarations::moduleLikeItem(ItemType type)
<< ItemType::Probe
<< ItemType::PropertyOptions
<< ItemType::Scanner);
- item << nameProperty();
+ PropertyDeclaration nameDecl = nameProperty();
+ PropertyDeclaration::Flags nameFlags = nameDecl.flags();
+ nameFlags |= PropertyDeclaration::ReadOnlyFlag;
+ nameDecl.setFlags(nameFlags);
+ item << nameDecl;
item << conditionProperty();
PropertyDeclaration setupBuildEnvDecl(StringConstants::setupBuildEnvironmentProperty(),
PropertyDeclaration::Variant, QString(),
diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp
index f7c3824f2..9a19828bb 100644
--- a/src/lib/corelib/language/evaluator.cpp
+++ b/src/lib/corelib/language/evaluator.cpp
@@ -70,10 +70,9 @@ public:
mutable QHash<QString, JSValue> valueCache;
};
-static void convertToPropertyType_impl(ScriptEngine *engine,
- const QString &pathPropertiesBaseDir, const Item *item,
- const PropertyDeclaration& decl,
- const CodeLocation &location, JSValue &v);
+static void convertToPropertyType_impl(
+ ScriptEngine *engine, const QString &pathPropertiesBaseDir, const Item *item,
+ const PropertyDeclaration& decl, const Value *value, const CodeLocation &location, JSValue &v);
static int getEvalPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, uint32_t *plen,
JSValueConst obj);
static int getEvalProperty(JSContext *ctx, JSPropertyDescriptor *desc,
@@ -92,18 +91,18 @@ Evaluator::Evaluator(ScriptEngine *scriptEngine)
Evaluator::~Evaluator()
{
Set<JSValue> valuesToFree;
- for (const auto &data : qAsConst(m_scriptValueMap)) {
+ for (const auto &data : std::as_const(m_scriptValueMap)) {
const auto evalData = attachedPointer<EvaluationData>(data, m_scriptClass);
valuesToFree << data;
for (const JSValue cachedValue : evalData->valueCache)
JS_FreeValue(m_scriptEngine->context(), cachedValue);
delete evalData;
}
- for (const auto &scopes : qAsConst(m_fileContextScopesMap)) {
+ for (const auto &scopes : std::as_const(m_fileContextScopesMap)) {
valuesToFree << scopes.fileScope;
valuesToFree << scopes.importScope;
}
- for (const JSValue v : qAsConst(valuesToFree)) {
+ for (const JSValue v : std::as_const(valuesToFree)) {
JS_FreeValue(m_scriptEngine->context(), v);
}
m_scriptEngine->unregisterEvaluator(this);
@@ -202,7 +201,7 @@ bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const
void Evaluator::convertToPropertyType(const PropertyDeclaration &decl, const CodeLocation &loc,
JSValue &v)
{
- convertToPropertyType_impl(engine(), QString(), nullptr, decl, loc, v);
+ convertToPropertyType_impl(engine(), QString(), nullptr, decl, nullptr, loc, v);
}
JSValue Evaluator::scriptValue(const Item *item)
@@ -223,12 +222,12 @@ JSValue Evaluator::scriptValue(const Item *item)
return scriptValue;
}
-void Evaluator::onItemPropertyChanged(Item *item)
+void Evaluator::clearCache(const Item *item)
{
const auto data = attachedPointer<EvaluationData>(m_scriptValueMap.value(item),
m_scriptEngine->dataWithPtrClass());
if (data) {
- for (const auto value : qAsConst(data->valueCache))
+ for (const auto value : std::as_const(data->valueCache))
JS_FreeValue(m_scriptEngine->context(), value);
data->valueCache.clear();
}
@@ -309,13 +308,14 @@ static QString overriddenSourceDirectory(const Item *item, const QString &defaul
static void convertToPropertyType_impl(ScriptEngine *engine,
const QString &pathPropertiesBaseDir, const Item *item,
const PropertyDeclaration& decl,
- const CodeLocation &location, JSValue &v)
+ const Value *value, const CodeLocation &location, JSValue &v)
{
JSContext * const ctx = engine->context();
if (JS_IsUndefined(v) || JS_IsError(ctx, v) || JS_IsException(v))
return;
QString srcDir;
QString actualBaseDir;
+ const Item * const srcDirItem = value && value->scope() ? value->scope() : item;
if (item && !pathPropertiesBaseDir.isEmpty()) {
const VariantValueConstPtr itemSourceDir
= item->variantProperty(QStringLiteral("sourceDirectory"));
@@ -339,8 +339,8 @@ static void convertToPropertyType_impl(ScriptEngine *engine,
makeTypeError(engine, decl, location, v);
break;
}
- const QString srcDir = item ? overriddenSourceDirectory(item, actualBaseDir)
- : pathPropertiesBaseDir;
+ const QString srcDir = srcDirItem ? overriddenSourceDirectory(srcDirItem, actualBaseDir)
+ : pathPropertiesBaseDir;
if (!srcDir.isEmpty()) {
v = engine->toScriptValue(QDir::cleanPath(FileInfo::resolvePath(srcDir,
getJsString(ctx, v))));
@@ -353,8 +353,8 @@ static void convertToPropertyType_impl(ScriptEngine *engine,
makeTypeError(engine, decl, location, v);
break;
case PropertyDeclaration::PathList:
- srcDir = item ? overriddenSourceDirectory(item, actualBaseDir)
- : pathPropertiesBaseDir;
+ srcDir = srcDirItem ? overriddenSourceDirectory(srcDirItem, actualBaseDir)
+ : pathPropertiesBaseDir;
// Fall-through.
case PropertyDeclaration::StringList:
{
@@ -565,56 +565,53 @@ private:
setupConvenienceProperty(StringConstants::outerVar(), &extraScope, v);
}
if (value->sourceUsesOriginal()) {
- JSValue originalValue = JS_UNDEFINED;
+ JSValue originalJs = JS_UNDEFINED;
ScopedJsValue originalMgr(engine->context(), JS_UNDEFINED);
if (data->item->propertyDeclaration(*propertyName).isScalar()) {
const Item *item = itemOfProperty;
- if (item->type() == ItemType::Module || item->type() == ItemType::Export) {
- const QString errorMessage = Tr::tr("The special value 'original' cannot "
- "be used on the right-hand side of a property declaration.");
+
+ if (item->type() != ItemType::ModuleInstance
+ && item->type() != ItemType::ModuleInstancePlaceholder) {
+ const QString errorMessage = Tr::tr("The special value 'original' can only "
+ "be used with module properties.");
extraScope = throwError(engine->context(), errorMessage);
result.second = false;
return result;
}
- // TODO: Provide a dedicated item type for not-yet-instantiated things that
- // look like module instances in the AST visitor.
- if (item->type() == ItemType::ModuleInstance
- && !item->hasProperty(StringConstants::presentProperty())) {
- const QString errorMessage = Tr::tr("Trying to assign property '%1' "
- "on something that is not a module.").arg(*propertyName);
+ if (!value->scope()) {
+ const QString errorMessage = Tr::tr("The special value 'original' cannot "
+ "be used on the right-hand side of a property declaration.");
extraScope = throwError(engine->context(), errorMessage);
result.second = false;
return result;
}
- while (item->type() == ItemType::ModuleInstance)
- item = item->prototype();
- if (item->type() != ItemType::Module && item->type() != ItemType::Export) {
- const QString errorMessage = Tr::tr("The special value 'original' can only "
- "be used with module properties.");
- extraScope = throwError(engine->context(), errorMessage);
- result.second = false;
- return result;
+ ValuePtr original;
+ for (const ValuePtr &v : value->candidates()) {
+ if (!v->scope()) {
+ original = v;
+ break;
+ }
}
- const ValuePtr v = item->property(*propertyName);
// This can happen when resolving shadow products. The error will be ignored
// in that case.
- if (!v) {
+ if (!original) {
const QString errorMessage = Tr::tr("Error setting up 'original'.");
extraScope = throwError(engine->context(), errorMessage);
result.second = false;
return result;
}
- SVConverter converter(engine, object, v, item, propertyName, data, &originalValue);
+ SVConverter converter(engine, object, original, item, propertyName, data,
+ &originalJs);
converter.start();
} else {
- originalValue = engine->newArray(0, JsValueOwner::Caller);
- originalMgr.setValue(originalValue);
+ originalJs = engine->newArray(0, JsValueOwner::Caller);
+ originalMgr.setValue(originalJs);
}
- setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalValue);
+ setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalJs);
}
return result;
}
@@ -627,6 +624,13 @@ private:
}
}
+ void pushScopeRecursively(const Item *scope)
+ {
+ if (scope) {
+ pushScopeRecursively(scope->scope());
+ pushScope(data->evaluator->scriptValue(scope));
+ }
+ }
void pushItemScopes(const Item *item)
{
const Item *scope = item->scope();
@@ -698,18 +702,36 @@ private:
}
pushScope(fileCtxScopes.fileScope);
pushItemScopes(data->item);
- if (itemOfProperty->type() != ItemType::ModuleInstance) {
- // Own properties of module instances must not have the instance itself in the scope.
+ if ((itemOfProperty->type() != ItemType::ModuleInstance
+ && itemOfProperty->type() != ItemType::ModuleInstancePlaceholder) || !value->scope()) {
pushScope(*object);
}
- if (value->definingItem())
- pushItemScopes(value->definingItem());
+ pushScopeRecursively(value->scope());
pushScope(maybeExtraScope.first);
pushScope(fileCtxScopes.importScope);
if (alternative) {
ScopedJsValue sv(engine->context(), engine->evaluate(JsValueOwner::Caller,
alternative->condition.value, {}, 1, scopeChain));
if (JsException ex = engine->checkAndClearException(alternative->condition.location)) {
+
+ // This handles cases like the following:
+ // Depends { name: "cpp" }
+ // Properties {
+ // condition: qbs.targetOS.contains("darwin")
+ // bundle.isBundle: false
+ // }
+ // On non-Darwin platforms, bundle never gets instantiated, and thus bundle.isBundle
+ // has no scope, so the qbs item in the condition is not found.
+ // TODO: Ideally, we would never evaluate values in placeholder items, but
+ // there are currently several contexts where we do that, e.g. Export
+ // and Group items. Perhaps change that, or try to collect all such
+ // exceptions and don't try to evaluate other cases.
+ if (itemOfProperty->type() == ItemType::ModuleInstancePlaceholder) {
+ result.scriptValue = JS_UNDEFINED;
+ result.tryNextAlternative = false;
+ return result;
+ }
+
result.scriptValue = engine->throwError(ex.toErrorInfo().toString());
//result.scriptValue = JS_Throw(engine->context(), ex.takeValue());
//result.scriptValue = ex.takeValue();
@@ -756,6 +778,16 @@ private:
}
};
+static void convertToPropertyType(ScriptEngine *engine, const Item *item,
+ const PropertyDeclaration& decl, const Value *value, JSValue &v)
+{
+ if (value->type() == Value::VariantValueType && JS_IsUndefined(v) && !decl.isScalar()) {
+ v = engine->newArray(0, JsValueOwner::ScriptEngine); // QTBUG-51237
+ return;
+ }
+ convertToPropertyType_impl(engine, engine->evaluator()->pathPropertiesBaseDir(), item, decl,
+ value, value->location(), v);
+}
static QString resultToString(JSContext *ctx, const JSValue &scriptValue)
{
@@ -770,19 +802,15 @@ static QString resultToString(JSContext *ctx, const JSValue &scriptValue)
return getJsVariant(ctx, scriptValue).toString();
}
-static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationData *data,
- JSValue *result, const QString &propertyName,
- const ValuePtr &value)
+static void collectValuesFromNextChain(
+ ScriptEngine *engine, JSValue obj, const ValuePtr &value, const Item *itemOfProperty, const QString &name,
+ const EvaluationData *data, JSValue *result)
{
JSValueList lst;
- Set<Value *> &currentNextChain = data->evaluator->currentNextChain();
- Set<Value *> oldNextChain = currentNextChain;
- for (ValuePtr next = value; next; next = next->next())
- currentNextChain.insert(next.get());
-
for (ValuePtr next = value; next; next = next->next()) {
- ScopedJsValue v(engine->context(),
- data->evaluator->property(next->definingItem(), propertyName));
+ JSValue v = JS_UNDEFINED;
+ SVConverter svc(engine, &obj, next, itemOfProperty, &name, data, &v);
+ svc.start();
if (JsException ex = engine->checkAndClearException({})) {
const ScopedJsValueList l(engine->context(), lst);
*result = engine->throwError(ex.toErrorInfo().toString());
@@ -790,7 +818,9 @@ static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationDat
}
if (JS_IsUndefined(v))
continue;
- lst.push_back(v.release());
+ const PropertyDeclaration decl = data->item->propertyDeclaration(name);
+ convertToPropertyType(engine, data->item, decl, next.get(), v);
+ lst.push_back(JS_DupValue(engine->context(), v));
if (next->type() == Value::JSSourceValueType
&& std::static_pointer_cast<JSSourceValue>(next)->isExclusiveListValue()) {
// TODO: Why on earth do we keep the last _2_ elements?
@@ -803,12 +833,11 @@ static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationDat
break;
}
}
- currentNextChain = oldNextChain;
*result = engine->newArray(int(lst.size()), JsValueOwner::ScriptEngine);
quint32 k = 0;
JSContext * const ctx = engine->context();
- for (const JSValue &v : qAsConst(lst)) {
+ for (const JSValue &v : std::as_const(lst)) {
QBS_ASSERT(!JS_IsError(ctx, v), continue);
if (JS_IsArray(ctx, v)) {
const quint32 vlen = getJsIntProperty(ctx, v, StringConstants::lengthProperty());
@@ -822,23 +851,17 @@ static void collectValuesFromNextChain(ScriptEngine *engine, const EvaluationDat
setJsProperty(ctx, *result, StringConstants::lengthProperty(), JS_NewInt32(ctx, k));
}
-static void convertToPropertyType(ScriptEngine *engine, const Item *item,
- const PropertyDeclaration& decl, const Value *value, JSValue &v)
-{
- if (value->type() == Value::VariantValueType && JS_IsUndefined(v) && !decl.isScalar()) {
- v = engine->newArray(0, JsValueOwner::ScriptEngine); // QTBUG-51237
- return;
- }
- convertToPropertyType_impl(engine, engine->evaluator()->pathPropertiesBaseDir(), item, decl,
- value->location(), v);
-}
-
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)
{
Evaluator * const evaluator = data->evaluator;
+ const bool isModuleInstance = item->type() == ItemType::ModuleInstance;
for (; item; item = item->prototype()) {
+ if (isModuleInstance
+ && (item->type() == ItemType::Module || item->type() == ItemType::Export)) {
+ break; // There's no property in a prototype that's not also in the instance.
+ }
const ValuePtr value = item->ownProperty(name);
if (!value)
continue;
@@ -857,8 +880,8 @@ static EvalResult getEvalProperty(ScriptEngine *engine, JSValue obj, const Item
}
}
- if (value->next() && !evaluator->currentNextChain().contains(value.get())) {
- collectValuesFromNextChain(engine, data, &result, name, value);
+ if (value->next()) {
+ collectValuesFromNextChain(engine, obj, value, itemOfProperty, name, data, &result);
} else {
SVConverter converter(engine, &obj, value, itemOfProperty, &name, data, &result);
converter.start();
diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h
index f7aee8e46..d791a4c5d 100644
--- a/src/lib/corelib/language/evaluator.h
+++ b/src/lib/corelib/language/evaluator.h
@@ -98,12 +98,12 @@ public:
void setCachingEnabled(bool enabled) { m_valueCacheEnabled = enabled; }
bool cachingEnabled() const { return m_valueCacheEnabled; }
+ void clearCache(const Item *item);
PropertyDependencies &propertyDependencies() { return m_propertyDependencies; }
void clearPropertyDependencies() { m_propertyDependencies.clear(); }
std::stack<QualifiedId> &requestedProperties() { return m_requestedProperties; }
- Set<Value *> &currentNextChain() { return m_currentNextChain; }
void handleEvaluationError(const Item *item, const QString &name);
@@ -113,7 +113,7 @@ public:
bool isNonDefaultValue(const Item *item, const QString &name) const;
private:
- void onItemPropertyChanged(Item *item) override;
+ void onItemPropertyChanged(Item *item) override { clearCache(item); }
bool evaluateProperty(JSValue *result, const Item *item, const QString &name,
bool *propertyWasSet);
@@ -124,7 +124,6 @@ private:
QString m_pathPropertiesBaseDir;
PropertyDependencies m_propertyDependencies;
std::stack<QualifiedId> m_requestedProperties;
- Set<Value *> m_currentNextChain;
bool m_valueCacheEnabled = false;
};
@@ -134,12 +133,17 @@ void throwOnEvaluationError(ScriptEngine *engine,
class EvalCacheEnabler
{
public:
- EvalCacheEnabler(Evaluator *evaluator) : m_evaluator(evaluator)
+ EvalCacheEnabler(Evaluator *evaluator, const QString &baseDir) : m_evaluator(evaluator)
{
m_evaluator->setCachingEnabled(true);
+ m_evaluator->setPathPropertiesBaseDir(baseDir);
}
- ~EvalCacheEnabler() { m_evaluator->setCachingEnabled(false); }
+ ~EvalCacheEnabler()
+ {
+ m_evaluator->setCachingEnabled(false);
+ m_evaluator->clearPathPropertiesBaseDir();
+ }
private:
Evaluator * const m_evaluator;
diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp
index c58d2058d..647d05aa2 100644
--- a/src/lib/corelib/language/item.cpp
+++ b/src/lib/corelib/language/item.cpp
@@ -47,6 +47,7 @@
#include "value.h"
#include <api/languageinfo.h>
+#include <logging/categories.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/error.h>
@@ -89,7 +90,7 @@ Item *Item::clone() const
dup->m_modules = m_modules;
dup->m_children.reserve(m_children.size());
- for (const Item * const child : qAsConst(m_children)) {
+ for (const Item * const child : std::as_const(m_children)) {
Item *clonedChild = child->clone();
clonedChild->m_parent = dup;
dup->m_children.push_back(clonedChild);
@@ -103,11 +104,20 @@ Item *Item::clone() const
return dup;
}
+Item *Item::rootPrototype()
+{
+ Item *proto = this;
+ while (proto->prototype())
+ proto = proto->prototype();
+ return proto;
+}
+
QString Item::typeName() const
{
switch (type()) {
case ItemType::IdScope: return QStringLiteral("[IdScope]");
case ItemType::ModuleInstance: return QStringLiteral("[ModuleInstance]");
+ case ItemType::ModuleInstancePlaceholder: return QStringLiteral("[ModuleInstancePlaceholder]");
case ItemType::ModuleParameters: return QStringLiteral("[ModuleParametersInstance]");
case ItemType::ModulePrefix: return QStringLiteral("[ModulePrefix]");
case ItemType::Outer: return QStringLiteral("[Outer]");
@@ -216,9 +226,23 @@ PropertyDeclaration Item::propertyDeclaration(const QString &name, bool allowExp
void Item::addModule(const Item::Module &module)
{
- const auto it = std::lower_bound(m_modules.begin(), m_modules.end(), module);
- QBS_CHECK(it == m_modules.end() || (module.name != it->name && module.item != it->item));
- m_modules.insert(it, module);
+ if (!qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) {
+ QBS_CHECK(none_of(m_modules, [&](const Module &m) {
+ if (m.name != module.name)
+ return false;
+ if (!!module.productInfo != !!m.productInfo)
+ return true;
+ if (!module.productInfo)
+ return true;
+ if (module.productInfo->multiplexId == m.productInfo->multiplexId
+ && module.productInfo->profile == m.productInfo->profile) {
+ return true;
+ }
+ return false;
+ }));
+ }
+
+ m_modules.push_back(module);
}
void Item::setObserver(ItemObserver *observer) const
@@ -275,6 +299,15 @@ void Item::copyProperty(const QString &propertyName, Item *target) const
target->setProperty(propertyName, property(propertyName));
}
+void Item::overrideProperties(const QVariantMap &config, const QString &key,
+ const SetupProjectParameters &parameters, Logger &logger)
+{
+ const QVariant configMap = config.value(key);
+ if (configMap.isNull())
+ return;
+ overrideProperties(configMap.toMap(), QualifiedId(key), parameters, logger);
+}
+
static const char *valueType(const Value *v)
{
switch (v->type()) {
@@ -312,7 +345,7 @@ void Item::dump(int indentation) const
}
if (!m_children.empty())
qDebug("%schildren:", indent.constData());
- for (const Item * const child : qAsConst(m_children))
+ for (const Item * const child : std::as_const(m_children))
child->dump(indentation + 4);
if (prototype()) {
qDebug("%sprototype:", indent.constData());
@@ -390,9 +423,37 @@ void Item::overrideProperties(
handlePropertyError(error, parameters, logger);
continue;
}
- setProperty(it.key(),
- VariantValue::create(PropertyDeclaration::convertToPropertyType(
- it.value(), decl.type(), namePrefix, it.key())));
+ const auto overridenValue = VariantValue::create(PropertyDeclaration::convertToPropertyType(
+ it.value(), decl.type(), namePrefix, it.key()));
+ overridenValue->markAsSetByCommandLine();
+ setProperty(it.key(), overridenValue);
+ }
+}
+
+void Item::setModules(const Modules &modules)
+{
+ m_modules = modules;
+}
+
+Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString &reason, Item *module)
+{
+ qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")."
+ << "Creating dummy module for presence check.";
+ if (!module) {
+ module = Item::create(&pool, ItemType::ModuleInstance);
+ module->setFile(FileContext::create());
+ module->setProperty(StringConstants::nameProperty(), VariantValue::create(name));
+ }
+ module->setType(ItemType::ModuleInstance);
+ module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue());
+ return module;
+}
+
+void setScopeForDescendants(Item *item, Item *scope)
+{
+ for (Item * const child : item->children()) {
+ child->setScope(scope);
+ setScopeForDescendants(child, scope);
}
}
diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h
index 60d74a3f4..337cad78a 100644
--- a/src/lib/corelib/language/item.h
+++ b/src/lib/corelib/language/item.h
@@ -53,6 +53,7 @@
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
+#include <optional>
#include <vector>
namespace qbs {
@@ -75,18 +76,29 @@ class QBS_AUTOTEST_EXPORT Item : public QbsQmlJS::Managed
public:
struct Module
{
- Module()
- : item(nullptr), isProduct(false), required(true)
- {}
-
QualifiedId name;
- Item *item;
- bool isProduct;
- bool requiredValue = true; // base value of the required prop
- bool required;
- bool fallbackEnabled = true;
+ 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.
+
+ // All items that declared an explicit dependency on this module. Can contain any
+ // number of module instances and at most one product.
+ std::vector<Item *> loadingItems;
+
QVariantMap parameters;
VersionRange versionRange;
+
+ // The shorter this value, the "closer to the product" we consider the module,
+ // and the higher its precedence is when merging property values.
+ int maxDependsChainLength = 0;
+
+ bool required = true;
};
using Modules = std::vector<Module>;
using PropertyDeclarationMap = QMap<QString, PropertyDeclaration>;
@@ -99,19 +111,27 @@ public:
const QString &id() const { return m_id; }
const CodeLocation &location() const { return m_location; }
Item *prototype() const { return m_prototype; }
+ Item *rootPrototype();
Item *scope() const { return m_scope; }
Item *outerItem() const { return m_outerItem; }
Item *parent() const { return m_parent; }
const FileContextPtr &file() const { return m_file; }
const QList<Item *> &children() const { return m_children; }
+ QList<Item *> &children() { return m_children; }
Item *child(ItemType type, bool checkForMultiple = true) const;
const PropertyMap &properties() const { return m_properties; }
+ PropertyMap &properties() { return m_properties; }
const PropertyDeclarationMap &propertyDeclarations() const { return m_propertyDeclarations; }
PropertyDeclaration propertyDeclaration(const QString &name, bool allowExpired = true) const;
+
+ // The list of modules is ordered such that dependencies appear before the modules
+ // depending on them.
const Modules &modules() const { return m_modules; }
+ Modules &modules() { return m_modules; }
+
void addModule(const Module &module);
void removeModules() { m_modules.clear(); }
- void setModules(const Modules &modules) { m_modules = modules; }
+ void setModules(const Modules &modules);
ItemType type() const { return m_type; }
void setType(ItemType type) { m_type = type; }
@@ -149,6 +169,11 @@ public:
void copyProperty(const QString &propertyName, Item *target) const;
void overrideProperties(
const QVariantMap &config,
+ const QString &key,
+ const SetupProjectParameters &parameters,
+ Logger &logger);
+ void overrideProperties(
+ const QVariantMap &config,
const QualifiedId &namePrefix,
const SetupProjectParameters &parameters,
Logger &logger);
@@ -178,6 +203,10 @@ private:
inline bool operator<(const Item::Module &m1, const Item::Module &m2) { return m1.name < m2.name; }
+Item *createNonPresentModule(ItemPool &pool, const QString &name, const QString &reason,
+ Item *module);
+void setScopeForDescendants(Item *item, Item *scope);
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp
deleted file mode 100644
index a29b36320..000000000
--- a/src/lib/corelib/language/itemreader.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "itemreader.h"
-
-#include "itemreadervisitorstate.h"
-
-#include <tools/profiling.h>
-#include <tools/stlutils.h>
-
-#include <QtCore/qfileinfo.h>
-
-#include <algorithm>
-
-namespace qbs {
-namespace Internal {
-
-static void makePathsCanonical(QStringList &paths)
-{
- Internal::removeIf(paths, [](QString &p) {
- p = QFileInfo(p).canonicalFilePath();
- return p.isEmpty();
- });
-}
-
-ItemReader::ItemReader(Logger &logger)
- : m_visitorState(std::make_unique<ItemReaderVisitorState>(logger))
-{
-}
-
-ItemReader::~ItemReader() = default;
-
-void ItemReader::setSearchPaths(const QStringList &searchPaths)
-{
- m_searchPaths = searchPaths;
- makePathsCanonical(m_searchPaths);
- m_allSearchPaths.clear();
-}
-
-void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths)
-{
- m_extraSearchPaths.push_back(extraSearchPaths);
- makePathsCanonical(m_extraSearchPaths.back());
- m_allSearchPaths.clear();
-}
-
-void ItemReader::popExtraSearchPaths()
-{
- m_extraSearchPaths.pop_back();
- m_allSearchPaths.clear();
-}
-
-const std::vector<QStringList> &ItemReader::extraSearchPathsStack() const
-{
- return m_extraSearchPaths;
-}
-
-void ItemReader::setExtraSearchPathsStack(const std::vector<QStringList> &s)
-{
- m_extraSearchPaths = s;
- m_allSearchPaths.clear();
-}
-
-void ItemReader::clearExtraSearchPathsStack()
-{
- m_extraSearchPaths.clear();
- m_allSearchPaths.clear();
-}
-
-const QStringList &ItemReader::allSearchPaths() const
-{
- if (m_allSearchPaths.empty()) {
- std::for_each(m_extraSearchPaths.crbegin(), m_extraSearchPaths.crend(),
- [this] (const QStringList &paths) {
- m_allSearchPaths += paths;
- });
- m_allSearchPaths += m_searchPaths;
- m_allSearchPaths.removeDuplicates();
- }
- return m_allSearchPaths;
-}
-
-Item *ItemReader::readFile(const QString &filePath)
-{
- AccumulatingTimer readFileTimer(m_elapsedTime != -1 ? &m_elapsedTime : nullptr);
- return m_visitorState->readFile(filePath, allSearchPaths(), m_pool);
-}
-
-Item *ItemReader::readFile(const QString &filePath, const CodeLocation &referencingLocation)
-{
- try {
- return readFile(filePath);
- } catch (const ErrorInfo &e) {
- if (e.hasLocation())
- throw;
- throw ErrorInfo(e.toString(), referencingLocation);
- }
-}
-
-Set<QString> ItemReader::filesRead() const
-{
- return m_visitorState->filesRead();
-}
-
-void ItemReader::setEnableTiming(bool on)
-{
- m_elapsedTime = on ? 0 : -1;
-}
-
-void ItemReader::setDeprecationWarningMode(DeprecationWarningMode mode)
-{
- m_visitorState->setDeprecationWarningMode(mode);
-}
-
-} // namespace Internal
-} // namespace qbs
diff --git a/src/lib/corelib/language/itemtype.h b/src/lib/corelib/language/itemtype.h
index 465396f45..7e9900a5b 100644
--- a/src/lib/corelib/language/itemtype.h
+++ b/src/lib/corelib/language/itemtype.h
@@ -74,6 +74,7 @@ enum class ItemType {
// Internal items created mainly by the module loader.
IdScope,
ModuleInstance,
+ ModuleInstancePlaceholder,
ModuleParameters,
ModulePrefix,
Outer,
diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp
index ecfe09d71..33fd3c6a4 100644
--- a/src/lib/corelib/language/language.cpp
+++ b/src/lib/corelib/language/language.cpp
@@ -51,6 +51,7 @@
#include <buildgraph/rulegraph.h> // TODO: Move to language?
#include <buildgraph/transformer.h>
#include <jsextensions/jsextensions.h>
+#include <loader/productitemmultiplexer.h>
#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/buildgraphlocker.h>
@@ -60,6 +61,7 @@
#include <tools/qbsassert.h>
#include <tools/qttools.h>
#include <tools/scripttools.h>
+#include <tools/setupprojectparameters.h>
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
@@ -311,7 +313,7 @@ void ResolvedProduct::accept(BuildGraphVisitor *visitor) const
{
if (!buildData)
return;
- for (BuildGraphNode * const node : qAsConst(buildData->rootNodes()))
+ for (BuildGraphNode * const node : std::as_const(buildData->rootNodes()))
node->accept(visitor);
}
@@ -345,7 +347,7 @@ FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const
{
FileTags result;
std::unique_ptr<int> priority;
- for (const FileTaggerConstPtr &tagger : qAsConst(fileTaggers)) {
+ for (const FileTaggerConstPtr &tagger : std::as_const(fileTaggers)) {
for (const QRegularExpression &pattern : tagger->patterns()) {
if (pattern.match(fileName).hasMatch()) {
if (priority) {
@@ -423,18 +425,9 @@ QString ResolvedProduct::uniqueName() const
return uniqueName(name, multiplexConfigurationId);
}
-QString ResolvedProduct::fullDisplayName(const QString &name,
- const QString &multiplexConfigurationId)
-{
- QString result = name;
- if (!multiplexConfigurationId.isEmpty())
- result.append(QLatin1Char(' ')).append(multiplexIdToString(multiplexConfigurationId));
- return result;
-}
-
QString ResolvedProduct::fullDisplayName() const
{
- return fullDisplayName(name, multiplexConfigurationId);
+ return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId);
}
QString ResolvedProduct::profile() const
@@ -521,7 +514,7 @@ void ResolvedProject::accept(BuildGraphVisitor *visitor) const
{
for (const ResolvedProductPtr &product : products)
product->accept(visitor);
- for (const ResolvedProjectPtr &subProject : qAsConst(subProjects))
+ for (const ResolvedProjectPtr &subProject : std::as_const(subProjects))
subProject->accept(visitor);
}
@@ -548,7 +541,7 @@ std::vector<ResolvedProjectPtr> ResolvedProject::allSubProjects() const
std::vector<ResolvedProductPtr> ResolvedProject::allProducts() const
{
std::vector<ResolvedProductPtr> productList = products;
- for (const auto &subProject : qAsConst(subProjects))
+ for (const auto &subProject : std::as_const(subProjects))
productList << subProject->allProducts();
return productList;
}
@@ -560,11 +553,11 @@ void ResolvedProject::load(PersistentPool &pool)
[](const ResolvedProductPtr &p) {
if (!p->buildData)
return;
- for (BuildGraphNode * const node : qAsConst(p->buildData->allNodes())) {
+ for (BuildGraphNode * const node : std::as_const(p->buildData->allNodes())) {
node->product = p;
// restore parent links
- for (BuildGraphNode * const child : qAsConst(node->children))
+ for (BuildGraphNode * const child : std::as_const(node->children))
child->parents.insert(node);
}
});
@@ -617,6 +610,16 @@ void TopLevelProject::makeModuleProvidersNonTransient()
m.transientOutput = false;
}
+QVariantMap TopLevelProject::fullProfileConfigsTree() const
+{
+ QVariantMap tree;
+ for (auto it = profileConfigs.cbegin(); it != profileConfigs.cend(); ++it) {
+ tree.insert(it.key(), SetupProjectParameters::finalBuildConfigurationTree(
+ it.value().toMap(), overriddenValues));
+ }
+ return tree;
+}
+
QString TopLevelProject::buildGraphFilePath() const
{
return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id());
@@ -663,7 +666,7 @@ void TopLevelProject::store(PersistentPool &pool)
void TopLevelProject::cleanupModuleProviderOutput()
{
QString error;
- for (const ModuleProviderInfo &m : qAsConst(moduleProviderInfo.providers)) {
+ for (const ModuleProviderInfo &m : std::as_const(moduleProviderInfo.providers)) {
if (m.transientOutput) {
if (!removeDirectoryWithContents(m.outputDirPath(buildDirectory), &error))
qCWarning(lcBuildGraph) << "Error removing module provider output:" << error;
@@ -913,11 +916,6 @@ bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1,
return listsAreEqual(l1, l2);
}
-QString multiplexIdToString(const QString &id)
-{
- return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8()));
-}
-
bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b)
{
return equals(a.m_sharedData.get(), b.m_sharedData.get());
diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h
index a1115519d..1dae572a1 100644
--- a/src/lib/corelib/language/language.h
+++ b/src/lib/corelib/language/language.h
@@ -361,7 +361,7 @@ public:
static ResolvedModulePtr create() { return ResolvedModulePtr(new ResolvedModule); }
QString name;
- QStringList moduleDependencies;
+ QStringList moduleDependencies; // TODO: Still needed?
PrivateScriptFunction setupBuildEnvironmentScript;
PrivateScriptFunction setupRunEnvironmentScript;
ResolvedProduct *product = nullptr;
@@ -597,7 +597,6 @@ public:
static QString uniqueName(const QString &name,
const QString &multiplexConfigurationId);
QString uniqueName() const;
- static QString fullDisplayName(const QString &name, const QString &multiplexConfigurationId);
QString fullDisplayName() const;
QString profile() const;
@@ -707,6 +706,7 @@ public:
QString id() const { return m_id; }
QString profile() const;
void makeModuleProvidersNonTransient();
+ QVariantMap fullProfileConfigsTree() const; // Tree-ified + overridden values
QVariantMap profileConfigs;
QVariantMap overriddenValues;
@@ -737,8 +737,6 @@ private:
bool artifactPropertyListsAreEqual(const std::vector<ArtifactPropertiesPtr> &l1,
const std::vector<ArtifactPropertiesPtr> &l2);
-QString multiplexIdToString(const QString &id);
-
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp
deleted file mode 100644
index 1156b138b..000000000
--- a/src/lib/corelib/language/loader.cpp
+++ /dev/null
@@ -1,213 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "loader.h"
-
-#include "evaluator.h"
-#include "language.h"
-#include "moduleloader.h"
-#include "projectresolver.h"
-#include "scriptengine.h"
-
-#include <logging/translator.h>
-#include <tools/fileinfo.h>
-#include <tools/profile.h>
-#include <tools/progressobserver.h>
-#include <tools/qbsassert.h>
-#include <tools/settings.h>
-#include <tools/setupprojectparameters.h>
-#include <tools/stringconstants.h>
-
-#include <QtCore/qdir.h>
-#include <QtCore/qobject.h>
-
-namespace qbs {
-namespace Internal {
-
-Loader::Loader(ScriptEngine *engine, Logger logger)
- : m_logger(std::move(logger))
- , m_progressObserver(nullptr)
- , m_engine(engine)
-{
- m_logger.storeWarnings();
-}
-
-void Loader::setProgressObserver(ProgressObserver *observer)
-{
- m_progressObserver = observer;
-}
-
-void Loader::setSearchPaths(const QStringList &_searchPaths)
-{
- QStringList searchPaths;
- for (const QString &searchPath : _searchPaths) {
- if (!FileInfo::exists(searchPath)) {
- m_logger.qbsWarning() << Tr::tr("Search path '%1' does not exist.")
- .arg(QDir::toNativeSeparators(searchPath));
- } else {
- searchPaths += searchPath;
- }
- }
-
- m_searchPaths = searchPaths;
-}
-
-void Loader::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
-{
- m_oldProjectProbes = oldProbes;
-}
-
-void Loader::setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
-{
- m_oldProductProbes = oldProbes;
-}
-
-void Loader::setStoredProfiles(const QVariantMap &profiles)
-{
- m_storedProfiles = profiles;
-}
-
-void Loader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo)
-{
- m_storedModuleProviderInfo = providerInfo;
-}
-
-TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters &_parameters)
-{
- SetupProjectParameters parameters = _parameters;
-
- if (parameters.topLevelProfile().isEmpty()) {
- Settings settings(parameters.settingsDirectory());
- QString profileName = settings.defaultProfile();
- if (profileName.isEmpty()) {
- m_logger.qbsDebug() << Tr::tr("No profile specified and no default profile exists. "
- "Using default property values.");
- profileName = Profile::fallbackName();
- }
- parameters.setTopLevelProfile(profileName);
- parameters.expandBuildConfiguration();
- }
-
- setupProjectFilePath(parameters);
- QBS_CHECK(QFileInfo(parameters.projectFilePath()).isAbsolute());
- m_logger.qbsDebug() << "Using project file '"
- << QDir::toNativeSeparators(parameters.projectFilePath()) << "'.";
-
- m_engine->setEnvironment(parameters.adjustedEnvironment());
- m_engine->checkAndClearException({});
- m_engine->clearImportsCache();
- m_engine->clearRequestedProperties();
- m_engine->enableProfiling(parameters.logElapsedTime());
- m_logger.clearWarnings();
- EvalContextSwitcher evalContextSwitcher(m_engine, EvalContext::PropertyEvaluation);
-
- // At this point, we cannot set a sensible total effort, because we know nothing about
- // the project yet. That's why we use a placeholder here, so the user at least
- // sees that an operation is starting. The real total effort will be set later when
- // we have enough information.
- if (m_progressObserver) {
- m_progressObserver->initialize(Tr::tr("Resolving project for configuration %1")
- .arg(TopLevelProject::deriveId(parameters.finalBuildConfigurationTree())), 1);
- m_progressObserver->setScriptEngine(m_engine);
- }
-
- const FileTime resolveTime = FileTime::currentTime();
- Evaluator evaluator(m_engine);
- ModuleLoader moduleLoader(&evaluator, m_logger);
- moduleLoader.setProgressObserver(m_progressObserver);
- moduleLoader.setSearchPaths(m_searchPaths);
- moduleLoader.setOldProjectProbes(m_oldProjectProbes);
- moduleLoader.setOldProductProbes(m_oldProductProbes);
- moduleLoader.setLastResolveTime(m_lastResolveTime);
- moduleLoader.setStoredProfiles(m_storedProfiles);
- moduleLoader.setStoredModuleProviderInfo(m_storedModuleProviderInfo);
- const ModuleLoaderResult loadResult = moduleLoader.load(parameters);
- ProjectResolver resolver(&evaluator, loadResult, std::move(parameters), m_logger);
- resolver.setProgressObserver(m_progressObserver);
- TopLevelProjectPtr project = resolver.resolve();
- project->lastStartResolveTime = resolveTime;
- project->lastEndResolveTime = FileTime::currentTime();
-
- // E.g. if the top-level project is disabled.
- if (m_progressObserver)
- m_progressObserver->setFinished();
-
- return project;
-}
-
-void Loader::setupProjectFilePath(SetupProjectParameters &parameters)
-{
- QString projectFilePath = parameters.projectFilePath();
- if (projectFilePath.isEmpty())
- projectFilePath = QDir::currentPath();
- const QFileInfo projectFileInfo(projectFilePath);
- if (!projectFileInfo.exists())
- throw ErrorInfo(Tr::tr("Project file '%1' cannot be found.").arg(projectFilePath));
- if (projectFileInfo.isRelative())
- projectFilePath = projectFileInfo.absoluteFilePath();
- if (projectFileInfo.isFile()) {
- parameters.setProjectFilePath(projectFilePath);
- return;
- }
- if (!projectFileInfo.isDir())
- throw ErrorInfo(Tr::tr("Project file '%1' has invalid type.").arg(projectFilePath));
-
- const QStringList &actualFileNames
- = QDir(projectFilePath).entryList(StringConstants::qbsFileWildcards(), QDir::Files);
- if (actualFileNames.empty()) {
- QString error;
- if (parameters.projectFilePath().isEmpty())
- error = Tr::tr("No project file given and none found in current directory.\n");
- else
- error = Tr::tr("No project file found in directory '%1'.").arg(projectFilePath);
- throw ErrorInfo(error);
- }
- if (actualFileNames.size() > 1) {
- throw ErrorInfo(Tr::tr("More than one project file found in directory '%1'.")
- .arg(projectFilePath));
- }
- projectFilePath.append(QLatin1Char('/')).append(actualFileNames.front());
-
- projectFilePath = QDir::current().filePath(projectFilePath);
- projectFilePath = QDir::cleanPath(projectFilePath);
- parameters.setProjectFilePath(projectFilePath);
-}
-
-} // namespace Internal
-} // namespace qbs
diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp
deleted file mode 100644
index 1663fccac..000000000
--- a/src/lib/corelib/language/moduleloader.cpp
+++ /dev/null
@@ -1,3825 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "moduleloader.h"
-
-#include "builtindeclarations.h"
-#include "evaluator.h"
-#include "filecontext.h"
-#include "item.h"
-#include "itemreader.h"
-#include "language.h"
-#include "modulemerger.h"
-#include "moduleproviderloader.h"
-#include "probesresolver.h"
-#include "qualifiedid.h"
-#include "scriptengine.h"
-#include "value.h"
-
-#include <api/languageinfo.h>
-#include <language/language.h>
-#include <logging/categories.h>
-#include <logging/logger.h>
-#include <logging/translator.h>
-#include <tools/error.h>
-#include <tools/fileinfo.h>
-#include <tools/preferences.h>
-#include <tools/profile.h>
-#include <tools/profiling.h>
-#include <tools/progressobserver.h>
-#include <tools/qbsassert.h>
-#include <tools/qttools.h>
-#include <tools/scripttools.h>
-#include <tools/settings.h>
-#include <tools/stlutils.h>
-#include <tools/stringconstants.h>
-
-#include <QtCore/qdebug.h>
-#include <QtCore/qdir.h>
-#include <QtCore/qglobalstatic.h>
-#include <QtCore/qdiriterator.h>
-#include <QtCore/qjsondocument.h>
-#include <QtCore/qjsonobject.h>
-#include <QtCore/qtextstream.h>
-#include <QtCore/qthreadstorage.h>
-
-#include <algorithm>
-#include <memory>
-#include <utility>
-
-namespace qbs {
-namespace Internal {
-
-using MultiplexConfigurationByIdTable = QThreadStorage<QHash<QString, QVariantMap> >;
-Q_GLOBAL_STATIC(MultiplexConfigurationByIdTable, multiplexConfigurationsById);
-
-static bool 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 true;
-}
-
-class ModuleLoader::ItemModuleList : public QList<Item::Module> {};
-
-class ModuleLoader::ProductSortByDependencies
-{
-public:
- ProductSortByDependencies(TopLevelProjectContext &tlp) : m_tlp(tlp)
- {
- }
-
- void apply()
- {
- QHash<QString, std::vector<ProductContext *>> productsMap;
- QList<ProductContext *> allProducts;
- for (ProjectContext * const projectContext : qAsConst(m_tlp.projects)) {
- for (auto &product : projectContext->products) {
- allProducts.push_back(&product);
- productsMap[product.name].push_back(&product);
- }
- }
- Set<ProductContext *> allDependencies;
- for (auto productContext : qAsConst(allProducts)) {
- auto &productDependencies = m_dependencyMap[productContext];
- for (const auto &dep : qAsConst(productContext->info.usedProducts)) {
- QBS_CHECK(!dep.name.isEmpty());
- const auto &deps = productsMap.value(dep.name);
- if (dep.profile == StringConstants::star()) {
- QBS_CHECK(!deps.empty());
- for (ProductContext *depProduct : deps) {
- if (depProduct == productContext)
- continue;
- productDependencies.push_back(depProduct);
- allDependencies << depProduct;
- }
- } else {
- auto it = std::find_if(deps.begin(), deps.end(), [&dep] (ProductContext *p) {
- return p->multiplexConfigurationId == dep.multiplexConfigurationId;
- });
- if (it == deps.end()) {
- QBS_CHECK(!productContext->multiplexConfigurationId.isEmpty());
- const QString productName = ResolvedProduct::fullDisplayName(
- productContext->name, productContext->multiplexConfigurationId);
- const QString depName = ResolvedProduct::fullDisplayName(
- dep.name, dep.multiplexConfigurationId);
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not "
- "fulfilled.").arg(productName, depName),
- productContext->item->location());
- }
- productDependencies.push_back(*it);
- allDependencies << *it;
- }
- }
- }
- const Set<ProductContext *> rootProducts
- = rangeTo<Set<ProductContext *>>(allProducts) - allDependencies;
- for (ProductContext * const rootProduct : rootProducts)
- traverse(rootProduct);
- if (m_sortedProducts.size() < allProducts.size()) {
- for (auto const product : qAsConst(allProducts)) {
- QList<ModuleLoader::ProductContext *> path;
- findCycle(product, path);
- }
- }
- QBS_CHECK(m_sortedProducts.size() == allProducts.size());
- }
-
- // No product at position i has dependencies to a product at position j > i.
- const QList<ProductContext *> &sortedProducts() const
- {
- return m_sortedProducts;
- }
-
-private:
- void traverse(ModuleLoader::ProductContext *product)
- {
- if (!m_seenProducts.insert(product).second)
- return;
- for (const auto &dependency : m_dependencyMap.value(product))
- traverse(dependency);
- m_sortedProducts << product;
- }
-
- void findCycle(ModuleLoader::ProductContext *product,
- QList<ModuleLoader::ProductContext *> &path)
- {
- if (path.contains(product)) {
- ErrorInfo error(Tr::tr("Cyclic dependencies detected."));
- for (const auto * const p : path)
- error.append(p->name, p->item->location());
- error.append(product->name, product->item->location());
- throw error;
- }
- path << product;
- for (auto const dep : m_dependencyMap.value(product))
- findCycle(dep, path);
- path.removeLast();
- }
-
- TopLevelProjectContext &m_tlp;
- QHash<ProductContext *, std::vector<ProductContext *>> m_dependencyMap;
- Set<ProductContext *> m_seenProducts;
- QList<ProductContext *> m_sortedProducts;
-};
-
-class SearchPathsManager {
-public:
- explicit SearchPathsManager(ItemReader *itemReader)
- : m_itemReader(itemReader)
- , m_oldSize(itemReader->extraSearchPathsStack().size())
- {
- }
- SearchPathsManager(ItemReader *itemReader, const QStringList &extraSearchPaths)
- : SearchPathsManager(itemReader)
- {
- m_itemReader->pushExtraSearchPaths(extraSearchPaths);
- }
- ~SearchPathsManager()
- {
- reset();
- }
- void reset()
- {
- while (m_itemReader->extraSearchPathsStack().size() > m_oldSize)
- m_itemReader->popExtraSearchPaths();
- }
-
-private:
- ItemReader * const m_itemReader;
- size_t m_oldSize{0};
-};
-
-ModuleLoader::ModuleLoader(Evaluator *evaluator, Logger &logger)
- : m_pool(nullptr)
- , m_logger(logger)
- , m_progressObserver(nullptr)
- , m_reader(std::make_unique<ItemReader>(logger))
- , m_evaluator(evaluator)
- , m_probesResolver(std::make_unique<ProbesResolver>(m_evaluator, m_logger))
- , m_moduleProviderLoader(
- std::make_unique<ModuleProviderLoader>(m_reader.get(), m_evaluator, m_probesResolver.get(),
- m_logger))
-{
-}
-
-ModuleLoader::~ModuleLoader() = default;
-
-void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver)
-{
- m_progressObserver = progressObserver;
-}
-
-void ModuleLoader::setSearchPaths(const QStringList &searchPaths)
-{
- m_reader->setSearchPaths(searchPaths);
- qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths;
-}
-
-void ModuleLoader::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
-{
- m_probesResolver->setOldProjectProbes(oldProbes);
-}
-
-void ModuleLoader::setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
-{
- m_probesResolver->setOldProductProbes(oldProbes);
-}
-
-void ModuleLoader::setStoredProfiles(const QVariantMap &profiles)
-{
- m_storedProfiles = profiles;
-}
-
-void ModuleLoader::setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo)
-{
- m_moduleProviderLoader->setStoredModuleProviderInfo(moduleProviderInfo);
-}
-
-ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters &parameters)
-{
- TimedActivityLogger moduleLoaderTimer(m_logger, Tr::tr("ModuleLoader"),
- parameters.logElapsedTime());
- qCDebug(lcModuleLoader) << "load" << parameters.projectFilePath();
- m_parameters = parameters;
- m_modulePrototypes.clear();
- m_modulePrototypeEnabledInfo.clear();
- m_parameterDeclarations.clear();
- m_disabledItems.clear();
- m_reader->setDeprecationWarningMode(parameters.deprecationWarningMode());
- m_reader->clearExtraSearchPathsStack();
- m_reader->setEnableTiming(parameters.logElapsedTime());
- m_moduleProviderLoader->setProjectParameters(m_parameters);
- m_probesResolver->setProjectParameters(m_parameters);
- m_elapsedTimePrepareProducts = m_elapsedTimeHandleProducts
- = m_elapsedTimeProductDependencies = m_elapsedTimeTransitiveDependencies
- = m_elapsedTimePropertyChecking = 0;
- m_elapsedTimeModuleProviders = 0;
- m_settings = std::make_unique<Settings>(parameters.settingsDirectory());
-
- const auto keys = m_parameters.overriddenValues().keys();
- for (const QString &key : keys) {
- static const QStringList prefixes({ StringConstants::projectPrefix(),
- QStringLiteral("projects"),
- QStringLiteral("products"), QStringLiteral("modules"),
- StringConstants::moduleProviders(),
- StringConstants::qbsModule()});
- bool ok = false;
- for (const auto &prefix : prefixes) {
- if (key.startsWith(prefix + QLatin1Char('.'))) {
- ok = true;
- break;
- }
- }
- if (ok) {
- collectNameFromOverride(key);
- continue;
- }
- ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(key));
- e.append(Tr::tr("Please use one of the following:"));
- e.append(QLatin1Char('\t') + Tr::tr("projects.<project-name>.<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("modules.<module-name>.<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("products.<product-name>.<module-name>."
- "<property-name>:value"));
- e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.<provider-name>."
- "<property-name>:value"));
- handlePropertyError(e, m_parameters, m_logger);
- }
-
- ModuleLoaderResult result;
- result.profileConfigs = m_storedProfiles;
- m_pool = result.itemPool.get();
- m_reader->setPool(m_pool);
-
- const QStringList topLevelSearchPaths = parameters.finalBuildConfigurationTree()
- .value(StringConstants::projectPrefix()).toMap()
- .value(StringConstants::qbsSearchPathsProperty()).toStringList();
- Item *root;
- {
- SearchPathsManager searchPathsManager(m_reader.get(), topLevelSearchPaths);
- root = loadItemFromFile(parameters.projectFilePath(), CodeLocation());
- if (!root)
- return ModuleLoaderResult();
- }
-
- switch (root->type()) {
- case ItemType::Product:
- root = wrapInProjectIfNecessary(root);
- break;
- case ItemType::Project:
- break;
- default:
- throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it"
- " is of type '%1'.").arg(root->typeName()), root->location());
- }
-
- const QString buildDirectory = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(),
- TopLevelProject::deriveId(parameters.finalBuildConfigurationTree()));
- root->setProperty(StringConstants::sourceDirectoryProperty(),
- VariantValue::create(QFileInfo(root->file()->filePath()).absolutePath()));
- root->setProperty(StringConstants::buildDirectoryProperty(),
- VariantValue::create(buildDirectory));
- root->setProperty(StringConstants::profileProperty(),
- VariantValue::create(m_parameters.topLevelProfile()));
- handleTopLevelProject(&result, root, buildDirectory,
- Set<QString>() << QDir::cleanPath(parameters.projectFilePath()));
- result.root = root;
- result.qbsFiles = m_reader->filesRead() - m_moduleProviderLoader->tempQbsFiles();
- for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it)
- result.profileConfigs.remove(it.key());
- printProfilingInfo();
- return result;
-}
-
-class PropertyDeclarationCheck : public ValueHandler
-{
- const Set<Item *> &m_disabledItems;
- Set<Item *> m_handledItems;
- std::vector<Item *> m_parentItems;
- Item *m_currentModuleInstance = nullptr;
- QualifiedId m_currentModuleName;
- QString m_currentName;
- SetupProjectParameters m_params;
- Logger &m_logger;
-public:
- PropertyDeclarationCheck(const Set<Item *> &disabledItems,
- SetupProjectParameters params, Logger &logger)
- : m_disabledItems(disabledItems)
- , m_params(std::move(params))
- , m_logger(logger)
- {
- }
-
- void operator()(Item *item)
- {
- handleItem(item);
- }
-
-private:
- void handle(JSSourceValue *value) override
- {
- if (!value->createdByPropertiesBlock()) {
- const ErrorInfo error(Tr::tr("Property '%1' is not declared.")
- .arg(m_currentName), value->location());
- handlePropertyError(error, m_params, m_logger);
- }
- }
-
- void handle(ItemValue *value) override
- {
- if (checkItemValue(value))
- handleItem(value->item());
- }
-
- bool checkItemValue(ItemValue *value)
- {
- // TODO: Remove once QBS-1030 is fixed.
- if (parentItem()->type() == ItemType::Artifact)
- return false;
-
- if (parentItem()->type() == ItemType::Properties)
- return false;
-
- if (parentItem()->isOfTypeOrhasParentOfType(ItemType::Export)) {
- // Export item prototypes do not have instantiated modules.
- // The module instances are where the Export is used.
- QBS_ASSERT(m_currentModuleInstance, return false);
- auto hasCurrentModuleName = [this](const Item::Module &m) {
- return m.name == m_currentModuleName;
- };
- if (any_of(m_currentModuleInstance->modules(), hasCurrentModuleName))
- return true;
- }
-
- // TODO: We really should have a dedicated item type for "pre-instantiated" item values
- // and only use ModuleInstance for actual module instances.
- const bool itemIsModuleInstance = value->item()->type() == ItemType::ModuleInstance
- && value->item()->hasProperty(StringConstants::presentProperty());
-
- if (!itemIsModuleInstance
- && value->item()->type() != ItemType::ModulePrefix
- && (!parentItem()->file() || !parentItem()->file()->idScope()
- || !parentItem()->file()->idScope()->hasProperty(m_currentName))
- && !value->createdByPropertiesBlock()) {
- CodeLocation location = value->location();
- for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i)
- location = m_parentItems.at(i)->location();
- const ErrorInfo error(Tr::tr("Item '%1' is not declared. "
- "Did you forget to add a Depends item?")
- .arg(m_currentModuleName.toString()), location);
- handlePropertyError(error, m_params, m_logger);
- return false;
- }
-
- return true;
- }
-
- void handleItem(Item *item)
- {
- if (!m_handledItems.insert(item).second)
- return;
- if (m_disabledItems.contains(item)
- || (item->type() == ItemType::ModuleInstance && !item->isPresentModule())
- || item->type() == ItemType::Properties
-
- // The Properties child of a SubProject item is not a regular item.
- || item->type() == ItemType::PropertiesInSubProject) {
- return;
- }
-
- // If a module was found but its validate script failed, only the canonical
- // module instance will have the "non-present" flag set, so we need to locate it.
- if (item->type() == ItemType::ModuleInstance) {
- const Item *productItem = nullptr;
- for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) {
- if ((*it)->type() == ItemType::Product) {
- productItem = *it;
- break;
- }
- }
- if (productItem) {
- for (const Item::Module &m : productItem->modules()) {
- if (m.name == m_currentModuleName) {
- if (!m.item->isPresentModule())
- return;
- break;
- }
- }
- }
- }
-
- m_parentItems.push_back(item);
- for (Item::PropertyMap::const_iterator it = item->properties().constBegin();
- it != item->properties().constEnd(); ++it) {
- if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders()
- && it.value()->type() == Value::ItemValueType)
- continue;
- const PropertyDeclaration decl = item->propertyDeclaration(it.key());
- if (decl.isValid()) {
- const ErrorInfo deprecationError = decl.checkForDeprecation(
- m_params.deprecationWarningMode(), it.value()->location(), m_logger);
- if (deprecationError.hasError())
- handlePropertyError(deprecationError, m_params, m_logger);
- continue;
- }
- m_currentName = it.key();
- const QualifiedId oldModuleName = m_currentModuleName;
- if (parentItem()->type() != ItemType::ModulePrefix)
- m_currentModuleName.clear();
- m_currentModuleName.push_back(m_currentName);
- it.value()->apply(this);
- m_currentModuleName = oldModuleName;
- }
- m_parentItems.pop_back();
- for (Item * const child : item->children()) {
- switch (child->type()) {
- case ItemType::Export:
- case ItemType::Depends:
- case ItemType::Parameter:
- case ItemType::Parameters:
- break;
- case ItemType::Group:
- if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance)
- break;
- Q_FALLTHROUGH();
- default:
- handleItem(child);
- }
- }
-
- // Properties that don't refer to an existing module with a matching Depends item
- // only exist in the prototype of an Export item, not in the instance.
- // Example 1 - setting a property of an unknown module: Export { abc.def: true }
- // Example 2 - setting a non-existing Export property: Export { blubb: true }
- if (item->type() == ItemType::ModuleInstance && item->prototype()) {
- Item *oldInstance = m_currentModuleInstance;
- m_currentModuleInstance = item;
- handleItem(item->prototype());
- m_currentModuleInstance = oldInstance;
- }
- }
-
- void handle(VariantValue *) override { /* only created internally - no need to check */ }
-
- Item *parentItem() const { return m_parentItems.back(); }
-};
-
-void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem,
- const QString &buildDirectory, const Set<QString> &referencedFilePaths)
-{
- TopLevelProjectContext tlp;
- tlp.buildDirectory = buildDirectory;
- handleProject(loadResult, &tlp, projectItem, referencedFilePaths);
- checkProjectNamesInOverrides(tlp);
- collectProductsByName(tlp);
- checkProductNamesInOverrides();
-
- adjustDependenciesForMultiplexing(tlp);
-
- m_dependencyResolvingPass = 1;
- for (ProjectContext * const projectContext : qAsConst(tlp.projects)) {
- m_reader->setExtraSearchPathsStack(projectContext->searchPathsStack);
- for (ProductContext &productContext : projectContext->products) {
- try {
- setupProductDependencies(&productContext, Set<DeferredDependsContext>());
- } catch (const ErrorInfo &err) {
- if (productContext.name.isEmpty())
- throw err;
- handleProductError(err, &productContext);
- }
- // extraSearchPathsStack is changed during dependency resolution, check
- // that we've rolled back all the changes
- QBS_CHECK(m_reader->extraSearchPathsStack() == projectContext->searchPathsStack);
- }
- }
- if (!m_productsWithDeferredDependsItems.empty() || !m_exportsWithDeferredDependsItems.empty()) {
- collectProductsByType(tlp);
- m_dependencyResolvingPass = 2;
-
- // Doing the normalization for the Export items themselves (as opposed to doing it only
- // for the corresponding module instances) serves two purposes:
- // (1) It makes recursive use of Depends.productTypes via Export items work; otherwise,
- // we'd need an additional dependency resolving pass for every export level.
- // (2) The "expanded" Depends items are available to the Exporter.qbs module.
- for (Item * const exportItem : m_exportsWithDeferredDependsItems)
- normalizeDependencies(nullptr, DeferredDependsContext(nullptr, exportItem));
-
- for (const auto &deferredDependsData : m_productsWithDeferredDependsItems) {
- ProductContext * const productContext = deferredDependsData.first;
- m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack);
- try {
- setupProductDependencies(productContext, deferredDependsData.second);
- } catch (const ErrorInfo &err) {
- handleProductError(err, productContext);
- }
- }
- }
-
- ProductSortByDependencies productSorter(tlp);
- productSorter.apply();
- for (ProductContext * const p : productSorter.sortedProducts()) {
- try {
- handleProduct(p);
- if (p->name.startsWith(StringConstants::shadowProductPrefix()))
- tlp.probes << p->info.probes;
- } catch (const ErrorInfo &err) {
- handleProductError(err, p);
- }
- }
-
- loadResult->projectProbes = tlp.probes;
- loadResult->storedModuleProviderInfo = m_moduleProviderLoader->storedModuleProviderInfo();
-
- m_reader->clearExtraSearchPathsStack();
- AccumulatingTimer timer(m_parameters.logElapsedTime()
- ? &m_elapsedTimePropertyChecking : nullptr);
- PropertyDeclarationCheck check(m_disabledItems, m_parameters, m_logger);
- check(projectItem);
-}
-
-void ModuleLoader::handleProject(ModuleLoaderResult *loadResult,
- TopLevelProjectContext *topLevelProjectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths)
-{
- auto p = std::make_unique<ProjectContext>();
- auto &projectContext = *p;
- projectContext.topLevelProject = topLevelProjectContext;
- projectContext.result = loadResult;
- ItemValuePtr itemValue = ItemValue::create(projectItem);
- projectContext.scope = Item::create(m_pool, ItemType::Scope);
- projectContext.scope->setFile(projectItem->file());
- projectContext.scope->setProperty(StringConstants::projectVar(), itemValue);
- ProductContext dummyProductContext;
- dummyProductContext.project = &projectContext;
- dummyProductContext.moduleProperties = m_parameters.finalBuildConfigurationTree();
- projectItem->addModule(loadBaseModule(&dummyProductContext, projectItem));
- overrideItemProperties(projectItem, StringConstants::projectPrefix(),
- m_parameters.overriddenValuesTree());
- projectContext.name = m_evaluator->stringValue(projectItem,
- StringConstants::nameProperty());
- if (projectContext.name.isEmpty()) {
- projectContext.name = FileInfo::baseName(projectItem->location().filePath());
- projectItem->setProperty(StringConstants::nameProperty(),
- VariantValue::create(projectContext.name));
- }
- overrideItemProperties(projectItem,
- StringConstants::projectsOverridePrefix() + projectContext.name,
- m_parameters.overriddenValuesTree());
- if (!checkItemCondition(projectItem)) {
- m_disabledProjects.insert(projectContext.name);
- return;
- }
- topLevelProjectContext->projects.push_back(p.release());
- SearchPathsManager searchPathsManager(m_reader.get(), readExtraSearchPaths(projectItem)
- << projectItem->file()->dirPath());
- projectContext.searchPathsStack = m_reader->extraSearchPathsStack();
- projectContext.item = projectItem;
-
- const QString minVersionStr
- = m_evaluator->stringValue(projectItem, StringConstants::minimumQbsVersionProperty(),
- QStringLiteral("1.3.0"));
- const Version minVersion = Version::fromString(minVersionStr);
- if (!minVersion.isValid()) {
- throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion "
- "is not a valid version string.").arg(minVersionStr), projectItem->location());
- }
- if (!m_qbsVersion.isValid())
- m_qbsVersion = Version::fromString(QLatin1String(QBS_VERSION));
- if (m_qbsVersion < minVersion) {
- throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but "
- "this is qbs version %2.").arg(minVersion.toString(),
- m_qbsVersion.toString()));
- }
-
- for (Item * const child : projectItem->children())
- child->setScope(projectContext.scope);
-
- m_probesResolver->resolveProbes(&dummyProductContext, projectItem);
- projectContext.topLevelProject->probes << dummyProductContext.info.probes;
-
- handleProfileItems(projectItem, &projectContext);
-
- QList<Item *> multiplexedProducts;
- for (Item * const child : projectItem->children()) {
- if (child->type() == ItemType::Product)
- multiplexedProducts << multiplexProductItem(&dummyProductContext, child);
- }
- for (Item * const additionalProductItem : qAsConst(multiplexedProducts))
- Item::addChild(projectItem, additionalProductItem);
-
- const QList<Item *> originalChildren = projectItem->children();
- for (Item * const child : originalChildren) {
- switch (child->type()) {
- case ItemType::Product:
- prepareProduct(&projectContext, child);
- break;
- case ItemType::SubProject:
- handleSubProject(&projectContext, child, referencedFilePaths);
- break;
- case ItemType::Project:
- copyProperties(projectItem, child);
- handleProject(loadResult, topLevelProjectContext, child, referencedFilePaths);
- break;
- default:
- break;
- }
- }
-
- const QStringList refs = m_evaluator->stringListValue(
- projectItem, StringConstants::referencesProperty());
- const CodeLocation referencingLocation
- = projectItem->property(StringConstants::referencesProperty())->location();
- QList<Item *> additionalProjectChildren;
- for (const QString &filePath : refs) {
- try {
- additionalProjectChildren << loadReferencedFile(filePath, referencingLocation,
- referencedFilePaths, dummyProductContext);
- } catch (const ErrorInfo &error) {
- if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict)
- throw;
- m_logger.printWarning(error);
- }
- }
- for (Item * const subItem : qAsConst(additionalProjectChildren)) {
- Item::addChild(projectContext.item, subItem);
- switch (subItem->type()) {
- case ItemType::Product:
- prepareProduct(&projectContext, subItem);
- break;
- case ItemType::Project:
- copyProperties(projectItem, subItem);
- handleProject(loadResult, topLevelProjectContext, subItem,
- Set<QString>(referencedFilePaths) << subItem->file()->filePath());
- break;
- default:
- break;
- }
- }
-}
-
-QString ModuleLoader::MultiplexInfo::toIdString(size_t row) const
-{
- const auto &mprow = table.at(row);
- QVariantMap multiplexConfiguration;
- for (size_t column = 0; column < mprow.size(); ++column) {
- const QString &propertyName = properties.at(column);
- const VariantValuePtr &mpvalue = mprow.at(column);
- multiplexConfiguration.insert(propertyName, mpvalue->value());
- }
- QString id = QString::fromUtf8(QJsonDocument::fromVariant(multiplexConfiguration)
- .toJson(QJsonDocument::Compact)
- .toBase64());
- // Cache for later use in:multiplexIdToVariantMap()
- multiplexConfigurationsById->localData().insert(id, multiplexConfiguration);
- return id;
-}
-
-QVariantMap ModuleLoader::MultiplexInfo::multiplexIdToVariantMap(const QString &multiplexId)
-{
- if (multiplexId.isEmpty())
- return QVariantMap();
-
- QVariantMap result = multiplexConfigurationsById->localData().value(multiplexId);
- // We assume that MultiplexInfo::toIdString() has been called for this
- // particular multiplex configuration.
- QBS_CHECK(!result.isEmpty());
- return result;
-}
-
-void qbs::Internal::ModuleLoader::ModuleLoader::dump(const ModuleLoader::MultiplexInfo &mpi)
-{
- QStringList header;
- for (const auto &str : mpi.properties)
- header << str;
- qDebug() << header;
-
- for (const auto &row : mpi.table) {
- QVariantList values;
- for (const auto &elem : row) {
- values << elem->value();
- }
- qDebug() << values;
- }
-}
-
-ModuleLoader::MultiplexTable ModuleLoader::combine(const MultiplexTable &table,
- const MultiplexRow &values)
-{
- MultiplexTable result;
- if (table.empty()) {
- result.resize(values.size());
- for (size_t i = 0; i < values.size(); ++i) {
- MultiplexRow row;
- row.resize(1);
- row[0] = values.at(i);
- result[i] = row;
- }
- } else {
- for (const auto &row : table) {
- for (const auto &value : values) {
- MultiplexRow newRow = row;
- newRow.push_back(value);
- result.push_back(newRow);
- }
- }
- }
- return result;
-}
-
-ModuleLoader::MultiplexInfo ModuleLoader::extractMultiplexInfo(Item *productItem,
- Item *qbsModuleItem)
-{
- static const QString mpmKey = QStringLiteral("multiplexMap");
-
- JSContext * const ctx = m_evaluator->engine()->context();
- const ScopedJsValue multiplexMap(ctx, m_evaluator->value(qbsModuleItem, mpmKey));
- const QStringList multiplexByQbsProperties = m_evaluator->stringListValue(
- productItem, StringConstants::multiplexByQbsPropertiesProperty());
-
- MultiplexInfo multiplexInfo;
- multiplexInfo.aggregate = m_evaluator->boolValue(
- productItem, StringConstants::aggregateProperty());
-
- const QString multiplexedType = m_evaluator->stringValue(
- productItem, StringConstants::multiplexedTypeProperty());
- if (!multiplexedType.isEmpty())
- multiplexInfo.multiplexedType = VariantValue::create(multiplexedType);
-
- Set<QString> uniqueMultiplexByQbsProperties;
- for (const QString &key : multiplexByQbsProperties) {
- const QString mappedKey = getJsStringProperty(ctx, multiplexMap, key);
- if (mappedKey.isEmpty())
- throw ErrorInfo(Tr::tr("There is no entry for '%1' in 'qbs.multiplexMap'.").arg(key));
-
- if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second) {
- throw ErrorInfo(Tr::tr("Duplicate entry '%1' in Product.%2.")
- .arg(mappedKey, StringConstants::multiplexByQbsPropertiesProperty()),
- productItem->location());
- }
-
- const ScopedJsValue arr(ctx, m_evaluator->value(qbsModuleItem, key));
- if (JS_IsUndefined(arr))
- continue;
- if (!JS_IsArray(ctx, arr))
- throw ErrorInfo(Tr::tr("Property '%1' must be an array.").arg(key));
-
- const quint32 arrlen = getJsIntProperty(ctx, arr, StringConstants::lengthProperty());
- if (arrlen == 0)
- continue;
-
- MultiplexRow mprow;
- mprow.resize(arrlen);
- QVariantList entriesForKey;
- for (quint32 i = 0; i < arrlen; ++i) {
- const ScopedJsValue sv(ctx, JS_GetPropertyUint32(ctx, arr, i));
- const QVariant value = getJsVariant(ctx, sv);
- if (entriesForKey.contains(value)) {
- throw ErrorInfo(Tr::tr("Duplicate entry '%1' in qbs.%2.")
- .arg(value.toString(), key), productItem->location());
- }
- entriesForKey << value;
- mprow[i] = VariantValue::create(value);
- }
- multiplexInfo.table = combine(multiplexInfo.table, mprow);
- multiplexInfo.properties.push_back(mappedKey);
- }
- return multiplexInfo;
-}
-
-template <typename T, typename F>
-T ModuleLoader::callWithTemporaryBaseModule(ProductContext *productContext, const F &func)
-{
- // Temporarily attach the qbs module here, in case we need to access one of its properties
- // to evaluate properties.
- const QString &qbsKey = StringConstants::qbsModule();
- Item *productItem = productContext->item;
- ValuePtr qbsValue = productItem->property(qbsKey); // Retrieve now to restore later.
- if (qbsValue)
- qbsValue = qbsValue->clone();
- const Item::Module qbsModule = loadBaseModule(productContext, productItem);
- productItem->addModule(qbsModule);
-
- auto &&result = func(qbsModule);
-
- // "Unload" the qbs module again.
- if (qbsValue)
- productItem->setProperty(qbsKey, qbsValue);
- else
- productItem->removeProperty(qbsKey);
- productItem->removeModules();
-
- return std::forward<T>(result);
-}
-
-QList<Item *> ModuleLoader::multiplexProductItem(ProductContext *dummyContext, Item *productItem)
-{
- QString productName;
- dummyContext->item = productItem;
- auto extractMultiplexInfoFromProduct
- = [this, productItem, &productName](const Item::Module &qbsModule) {
- // Overriding the product item properties must be done here already, because multiplexing
- // properties might depend on product properties.
- const QString &nameKey = StringConstants::nameProperty();
- productName = m_evaluator->stringValue(productItem, nameKey);
- if (productName.isEmpty()) {
- productName = FileInfo::completeBaseName(productItem->file()->filePath());
- productItem->setProperty(nameKey, VariantValue::create(productName));
- }
- overrideItemProperties(productItem, StringConstants::productsOverridePrefix() + productName,
- m_parameters.overriddenValuesTree());
-
- return extractMultiplexInfo(productItem, qbsModule.item);
- };
- const auto multiplexInfo
- = callWithTemporaryBaseModule<const MultiplexInfo>(dummyContext,
- extractMultiplexInfoFromProduct);
-
- if (multiplexInfo.table.size() > 1)
- productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue());
-
- VariantValuePtr productNameValue = VariantValue::create(productName);
-
- Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr;
- QList<Item *> additionalProductItems;
- std::vector<VariantValuePtr> multiplexConfigurationIdValues;
- for (size_t row = 0; row < multiplexInfo.table.size(); ++row) {
- Item *item = productItem;
- const auto &mprow = multiplexInfo.table.at(row);
- QBS_CHECK(mprow.size() == multiplexInfo.properties.size());
- if (row > 0) {
- item = productItem->clone();
- additionalProductItems.push_back(item);
- }
- const QString multiplexConfigurationId = multiplexInfo.toIdString(row);
- const VariantValuePtr multiplexConfigurationIdValue
- = VariantValue::create(multiplexConfigurationId);
- if (multiplexInfo.table.size() > 1 || aggregator) {
- multiplexConfigurationIdValues.push_back(multiplexConfigurationIdValue);
- item->setProperty(StringConstants::multiplexConfigurationIdProperty(),
- multiplexConfigurationIdValue);
- }
- if (multiplexInfo.multiplexedType)
- item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType);
- for (size_t column = 0; column < mprow.size(); ++column) {
- Item *qbsItem = moduleInstanceItem(item, StringConstants::qbsModule());
- const QString &propertyName = multiplexInfo.properties.at(column);
- const VariantValuePtr &mpvalue = mprow.at(column);
- qbsItem->setProperty(propertyName, mpvalue);
- }
- }
-
- if (aggregator) {
- additionalProductItems << aggregator;
-
- // Add dependencies to all multiplexed instances.
- for (const auto &v : multiplexConfigurationIdValues) {
- Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends);
- dependsItem->setProperty(StringConstants::nameProperty(), productNameValue);
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdProperty(), v);
- dependsItem->setProperty(StringConstants::profilesProperty(),
- VariantValue::create(QStringList()));
- dependsItem->setFile(aggregator->file());
- dependsItem->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- Item::addChild(aggregator, dependsItem);
- }
- }
-
- return additionalProductItems;
-}
-
-void ModuleLoader::normalizeDependencies(ProductContext *product,
- const DeferredDependsContext &dependsContext)
-{
- std::vector<Item *> dependsItemsToAdd;
- std::vector<Item *> dependsItemsToRemove;
- std::vector<Item *> deferredDependsItems;
- for (Item *dependsItem : dependsContext.parentItem->children()) {
- if (dependsItem->type() != ItemType::Depends)
- continue;
- bool productTypesIsSet;
- const FileTags productTypes = m_evaluator->fileTagsValue(dependsItem,
- StringConstants::productTypesProperty(), &productTypesIsSet);
- if (productTypesIsSet) {
- bool nameIsSet;
- m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(), QString(),
- &nameIsSet);
-
- // The second condition is for the case where the dependency comes from an Export item
- // that has itself been normalized in the mean time.
- if (nameIsSet && !dependsItem->variantProperty(StringConstants::nameProperty())) {
- throw ErrorInfo(Tr::tr("The 'productTypes' and 'name' properties are mutually "
- "exclusive."), dependsItem->location());
- }
-
- bool submodulesPropertySet;
- m_evaluator->stringListValue( dependsItem, StringConstants::submodulesProperty(),
- &submodulesPropertySet);
- if (submodulesPropertySet) {
- throw ErrorInfo(Tr::tr("The 'productTypes' and 'subModules' properties are "
- "mutually exclusive."), dependsItem->location());
- }
-
- // We ignore the "limitToSubProject" property for dependencies from Export items,
- // because we cannot make it work consistently, as the importing product is not
- // yet known when normalizing via an Export item.
- const bool limitToSubProject = dependsContext.parentItem->type() == ItemType::Product
- && m_evaluator->boolValue(dependsItem,
- StringConstants::limitToSubProjectProperty());
- static const auto hasSameSubProject
- = [](const ProductContext &product, const ProductContext &other) {
- for (const Item *otherParent = other.item->parent(); otherParent;
- otherParent = otherParent->parent()) {
- if (otherParent == product.item->parent())
- return true;
- }
- return false;
- };
- std::vector<const ProductContext *> matchingProducts;
- for (const FileTag &typeTag : productTypes) {
- const auto range = m_productsByType.equal_range(typeTag);
- for (auto it = range.first; it != range.second; ++it) {
- if (it->second != product
- && (!product || it->second->name != product->name)
- && (!limitToSubProject || hasSameSubProject(*product, *it->second))) {
- matchingProducts.push_back(it->second);
- }
- }
- }
- if (matchingProducts.empty()) {
- qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything."
- << dependsItem->location();
- dependsItemsToRemove.push_back(dependsItem);
- continue;
- }
- if (dependsContext.parentItem->type() != ItemType::Export)
- deferredDependsItems.push_back(dependsItem);
- for (std::size_t i = 1; i < matchingProducts.size(); ++i) {
- Item * const dependsClone = dependsItem->clone();
- dependsClone->setProperty(StringConstants::nameProperty(),
- VariantValue::create(matchingProducts.at(i)->name));
- dependsItemsToAdd.push_back(dependsClone);
- if (dependsContext.parentItem->type() != ItemType::Export)
- deferredDependsItems.push_back(dependsClone);
-
- }
- dependsItem->setProperty(StringConstants::nameProperty(),
- VariantValue::create(matchingProducts.front()->name));
- }
- }
- for (Item * const newDependsItem : dependsItemsToAdd)
- Item::addChild(dependsContext.parentItem, newDependsItem);
- for (Item * const dependsItem : dependsItemsToRemove)
- Item::removeChild(dependsContext.parentItem, dependsItem);
- if (!deferredDependsItems.empty()) {
- auto &allDeferredDependsItems
- = product->deferredDependsItems[dependsContext.exportingProductItem];
- allDeferredDependsItems.insert(allDeferredDependsItems.end(), deferredDependsItems.cbegin(),
- deferredDependsItems.cend());
- }
-}
-
-void ModuleLoader::adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp)
-{
- for (const ProjectContext * const project : tlp.projects) {
- for (const ProductContext &product : project->products)
- adjustDependenciesForMultiplexing(product);
- }
-}
-
-void ModuleLoader::adjustDependenciesForMultiplexing(const ModuleLoader::ProductContext &product)
-{
- for (Item *dependsItem : product.item->children()) {
- if (dependsItem->type() == ItemType::Depends)
- adjustDependenciesForMultiplexing(product, dependsItem);
- }
-}
-
-void ModuleLoader::adjustDependenciesForMultiplexing(const ProductContext &product,
- Item *dependsItem)
-{
- const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty());
- const bool productIsMultiplexed = !product.multiplexConfigurationId.isEmpty();
- if (name == product.name) {
- QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator.
- return;
- }
-
- bool profilesPropertyIsSet;
- const QStringList profiles = m_evaluator->stringListValue(dependsItem,
- StringConstants::profilesProperty(), &profilesPropertyIsSet);
-
- const auto productRange = m_productsByName.equal_range(name);
- if (productRange.first == productRange.second) {
- // Dependency is a module. Nothing to adjust.
- return;
- }
-
- 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
- hasNonMultiplexedDependency = true;
- }
- bool hasMultiplexedDependencies = !multiplexedDependencies.empty();
-
- // These are the allowed cases:
- // (1) Normal dependency with no multiplexing whatsoever.
- // (2) Both product and dependency are multiplexed.
- // (2a) The profiles property is not set, we want to depend on the best
- // matching variant.
- // (2b) The profiles property is set, we want to depend on all variants
- // with a matching profile.
- // (3) The product is not multiplexed, but the dependency is.
- // (3a) The profiles property is not set, the dependency has an aggregator.
- // We want to depend on the aggregator.
- // (3b) The profiles property is not set, the dependency does not have an
- // aggregator. We want to depend on all the multiplexed variants.
- // (3c) The profiles property is set, we want to depend on all variants
- // with a matching profile regardless of whether an aggregator exists or not.
- // (4) The product is multiplexed, but the dependency is not. We don't have to adapt
- // any Depends items.
- // (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;
-
- // (3a)
- if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet)
- return;
-
- QStringList multiplexIds;
- const ShadowProductInfo shadowProductInfo = getShadowProductInfo(product);
- const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name;
- const auto productMultiplexConfig =
- MultiplexInfo::multiplexIdToVariantMap(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(
- StringConstants::multiplexConfigurationIdProperty());
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- multiplexId);
- return;
-
- }
- // Otherwise collect partial matches and decide later
- const auto dependencyMultiplexConfig = MultiplexInfo::multiplexIdToVariantMap(
- dependency->multiplexConfigurationId);
-
- if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig))
- multiplexIds << dependency->multiplexConfigurationId;
- } else {
- // (2b), (3b) or (3c)
- const bool profileMatch = !profilesPropertyIsSet || profiles.empty()
- || profiles.contains(dependency->profileName);
- if (profileMatch)
- multiplexIds << depMultiplexId;
- }
- }
- if (multiplexIds.empty()) {
- const QString productName = ResolvedProduct::fullDisplayName(
- product.name, product.multiplexConfigurationId);
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. "
- "There are no eligible multiplex candidates.").arg(productName,
- name),
- dependsItem->location());
- }
-
- // In case of (2a), at most 1 match is allowed
- if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) {
- const QString productName = ResolvedProduct::fullDisplayName(
- product.name, product.multiplexConfigurationId);
- QStringList candidateNames;
- for (const auto &id : qAsConst(multiplexIds))
- candidateNames << ResolvedProduct::fullDisplayName(name, id);
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. "
- "Eligible multiplex candidates: %3.").arg(
- productName, name, candidateNames.join(QLatin1String(", "))),
- dependsItem->location());
- }
-
- dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
- VariantValue::create(multiplexIds));
-}
-
-void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productItem)
-{
- AccumulatingTimer timer(m_parameters.logElapsedTime()
- ? &m_elapsedTimePrepareProducts : nullptr);
- checkCancelation();
- qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath();
-
- ProductContext productContext;
- productContext.item = productItem;
- productContext.project = projectContext;
- productContext.name = m_evaluator->stringValue(productItem, StringConstants::nameProperty());
- QBS_CHECK(!productContext.name.isEmpty());
- const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule());
- if (!!qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) {
- qbsItemValue->item()->setProperty(StringConstants::nameProperty(),
- VariantValue::create(StringConstants::nameProperty()));
- auto evaluateQbsProfileProperty = [this](const Item::Module &qbsModule) {
- return m_evaluator->stringValue(qbsModule.item,
- StringConstants::profileProperty(), QString());
- };
- productContext.profileName
- = callWithTemporaryBaseModule<QString>(&productContext,
- evaluateQbsProfileProperty);
- } else {
- productContext.profileName = m_parameters.topLevelProfile();
- }
- productContext.multiplexConfigurationId = m_evaluator->stringValue(
- productItem, StringConstants::multiplexConfigurationIdProperty());
- QBS_CHECK(!productContext.profileName.isEmpty());
- const auto it = projectContext->result->profileConfigs.constFind(productContext.profileName);
- if (it == projectContext->result->profileConfigs.constEnd()) {
- const Profile profile(productContext.profileName, m_settings.get(), m_localProfiles);
- if (!profile.exists()) {
- ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()),
- productItem->location());
- handleProductError(error, &productContext);
- return;
- }
- const QVariantMap buildConfig = SetupProjectParameters::expandedBuildConfiguration(
- profile, m_parameters.configurationName());
- productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree(
- buildConfig, m_parameters.overriddenValues());
- projectContext->result->profileConfigs.insert(productContext.profileName,
- productContext.moduleProperties);
- } else {
- productContext.moduleProperties = it.value().toMap();
- }
- initProductProperties(productContext);
-
- ItemValuePtr itemValue = ItemValue::create(productItem);
- productContext.scope = Item::create(m_pool, ItemType::Scope);
- productContext.scope->setProperty(StringConstants::productVar(), itemValue);
- productContext.scope->setFile(productItem->file());
- productContext.scope->setScope(productContext.project->scope);
-
- const bool hasExportItems = mergeExportItems(productContext);
-
- setScopeForDescendants(productItem, productContext.scope);
-
- projectContext->products.push_back(productContext);
-
- if (!hasExportItems || getShadowProductInfo(productContext).first)
- return;
-
- // This "shadow product" exists only to pull in a dependency on the actual product
- // and nothing else, thus providing us with the pure environment that we need to
- // evaluate the product's exported properties in isolation in the project resolver.
- Item * const importer = Item::create(productItem->pool(), ItemType::Product);
- importer->setProperty(QStringLiteral("name"),
- VariantValue::create(StringConstants::shadowProductPrefix()
- + productContext.name));
- importer->setFile(productItem->file());
- importer->setLocation(productItem->location());
- importer->setScope(projectContext->scope);
- importer->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends);
- dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name));
- dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false));
- dependsItem->setFile(importer->file());
- dependsItem->setLocation(importer->location());
- dependsItem->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- Item::addChild(importer, dependsItem);
- Item::addChild(productItem, importer);
- prepareProduct(projectContext, importer);
-}
-
-void ModuleLoader::setupProductDependencies(ProductContext *productContext,
- const Set<DeferredDependsContext> &deferredDependsContext)
-{
- if (m_dependencyResolvingPass == 2) {
- for (const DeferredDependsContext &ctx : deferredDependsContext)
- normalizeDependencies(productContext, ctx);
- for (const auto &deferralData : productContext->deferredDependsItems) {
- for (Item * const deferredDependsItem : deferralData.second) {
-
- // Dependencies from Export items are handled in addProductModuleDependencies().
- if (deferredDependsItem->parent() == productContext->item)
- adjustDependenciesForMultiplexing(*productContext, deferredDependsItem);
- }
- }
- }
- AccumulatingTimer timer(m_parameters.logElapsedTime()
- ? &m_elapsedTimeProductDependencies : nullptr);
- checkCancelation();
- Item *item = productContext->item;
- qCDebug(lcModuleLoader) << "setupProductDependencies" << productContext->name
- << productContext->item->location();
-
- if (m_dependencyResolvingPass == 1)
- setSearchPathsForProduct(productContext);
-
- // Module providers may push some extra search paths which we will be cleared
- // by this SearchPathsManager. However, they will be also added to productContext->searchPaths
- // so second pass will take that into account
- SearchPathsManager searchPathsManager(m_reader.get(), productContext->searchPaths);
-
- DependsContext dependsContext;
- dependsContext.product = productContext;
- dependsContext.productDependencies = &productContext->info.usedProducts;
- resolveDependencies(&dependsContext, item, productContext);
- if (m_dependencyResolvingPass == 2
- || !containsKey(m_productsWithDeferredDependsItems, productContext)) {
- addProductModuleDependencies(productContext);
- }
- productContext->project->result->productInfos[item] = productContext->info;
-}
-
-// Leaf modules first.
-// TODO: Can this be merged with addTransitiveDependencies? Looks suspiciously similar.
-void ModuleLoader::createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules)
-{
- if (std::find_if(modules.cbegin(), modules.cend(),
- [parentModule](const Item::Module &m) { return m.name == parentModule.name;})
- != modules.cend()) {
- return;
- }
- for (const Item::Module &dep : parentModule.item->modules())
- createSortedModuleList(dep, modules);
- modules.push_back(parentModule);
-}
-
-Item::Modules ModuleLoader::modulesSortedByDependency(const Item *productItem)
-{
- QBS_CHECK(productItem->type() == ItemType::Product);
- Item::Modules sortedModules;
- const Item::Modules &unsortedModules = productItem->modules();
- for (const Item::Module &module : unsortedModules)
- createSortedModuleList(module, sortedModules);
- QBS_CHECK(sortedModules.size() == unsortedModules.size());
-
- // Make sure the top-level items stay the same.
- for (Item::Module &s : sortedModules) {
- for (const Item::Module &u : unsortedModules) {
- if (s.name == u.name) {
- s.item = u.item;
- break;
- }
- }
- }
- return sortedModules;
-}
-
-
-template<typename T> bool insertIntoSet(Set<T> &set, const T &value)
-{
- const auto insertionResult = set.insert(value);
- return insertionResult.second;
-}
-
-void ModuleLoader::setupReverseModuleDependencies(const Item::Module &module,
- ModuleDependencies &deps,
- QualifiedIdSet &seenModules)
-{
- if (!insertIntoSet(seenModules, module.name))
- return;
- for (const Item::Module &m : module.item->modules()) {
- deps[m.name].insert(module.name);
- setupReverseModuleDependencies(m, deps, seenModules);
- }
-}
-
-ModuleLoader::ModuleDependencies ModuleLoader::setupReverseModuleDependencies(const Item *product)
-{
- ModuleDependencies deps;
- QualifiedIdSet seenModules;
- for (const Item::Module &m : product->modules())
- setupReverseModuleDependencies(m, deps, seenModules);
- return deps;
-}
-
-void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext)
-{
- AccumulatingTimer timer(m_parameters.logElapsedTime() ? &m_elapsedTimeHandleProducts : nullptr);
- if (productContext->info.delayedError.hasError())
- return;
-
- Item * const item = productContext->item;
-
- m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack);
- SearchPathsManager searchPathsManager(m_reader.get(), productContext->searchPaths);
- addTransitiveDependencies(productContext);
-
- // It is important that dependent modules are merged after their dependency, because
- // the dependent module's merger potentially needs to replace module items that were
- // set by the dependency module's merger (namely, scopes of defining items; see
- // ModuleMerger::replaceItemInScopes()).
- Item::Modules topSortedModules = modulesSortedByDependency(item);
- ModuleMerger::merge(m_logger, item, productContext->name, &topSortedModules);
-
- // Re-sort the modules by name. This is more stable; see QBS-818.
- // The list of modules in the product now has the same order as before,
- // only the items have been replaced by their merged counterparts.
- Item::Modules lexicographicallySortedModules = topSortedModules;
- std::sort(lexicographicallySortedModules.begin(), lexicographicallySortedModules.end());
- item->setModules(lexicographicallySortedModules);
-
- for (const Item::Module &module : topSortedModules) {
- if (!module.item->isPresentModule())
- continue;
- try {
- m_probesResolver->resolveProbes(productContext, module.item);
- if (module.versionRange.minimum.isValid()
- || module.versionRange.maximum.isValid()) {
- if (module.versionRange.maximum.isValid()
- && module.versionRange.minimum >= module.versionRange.maximum) {
- throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module "
- "'%3'").arg(module.versionRange.minimum.toString(),
- module.versionRange.maximum.toString(),
- module.name.toString()));
- }
- const Version moduleVersion = Version::fromString(
- m_evaluator->stringValue(module.item,
- StringConstants::versionProperty()));
- if (moduleVersion < module.versionRange.minimum) {
- throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
- "at least %3.").arg(module.name.toString(),
- moduleVersion.toString(),
- module.versionRange.minimum.toString()));
- }
- if (module.versionRange.maximum.isValid()
- && moduleVersion >= module.versionRange.maximum) {
- throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be "
- "lower than %3.").arg(module.name.toString(),
- moduleVersion.toString(),
- module.versionRange.maximum.toString()));
- }
- }
- } catch (const ErrorInfo &error) {
- handleModuleSetupError(productContext, module, error);
- if (productContext->info.delayedError.hasError())
- return;
- }
- }
-
- m_probesResolver->resolveProbes(productContext, item);
-
- // Module validation must happen in an extra pass, after all Probes have been resolved.
- EvalCacheEnabler cacheEnabler(m_evaluator);
- for (const Item::Module &module : topSortedModules) {
- if (!module.item->isPresentModule())
- continue;
- try {
- m_evaluator->boolValue(module.item, StringConstants::validateProperty());
- for (const auto &dep : module.item->modules()) {
- if (dep.requiredValue && !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(productContext, module, error);
- if (productContext->info.delayedError.hasError())
- return;
- }
- }
-
- if (!checkItemCondition(item)) {
- const auto &exportsData = productContext->project->topLevelProject->productModules;
- for (auto it = exportsData.find(productContext->name);
- it != exportsData.end() && it.key() == productContext->name; ++it) {
- if (it.value().multiplexId == productContext->multiplexConfigurationId) {
- createNonPresentModule(productContext->name, QStringLiteral("disabled"),
- it.value().exportItem);
- break;
- }
- }
- }
-
- checkDependencyParameterDeclarations(productContext);
- copyGroupsFromModulesToProduct(*productContext);
-
- ModuleDependencies reverseModuleDeps;
- for (Item * const child : item->children()) {
- if (child->type() == ItemType::Group) {
- if (reverseModuleDeps.empty())
- reverseModuleDeps = setupReverseModuleDependencies(item);
- handleGroup(productContext, child, reverseModuleDeps);
- }
- }
- productContext->project->result->productInfos[item] = productContext->info;
-}
-
-static Item *rootPrototype(Item *item)
-{
- Item *modulePrototype = item;
- while (modulePrototype->prototype())
- modulePrototype = modulePrototype->prototype();
- return modulePrototype;
-}
-
-class DependencyParameterDeclarationCheck
-{
-public:
- DependencyParameterDeclarationCheck(const QString &productName, const Item *productItem,
- const QHash<const Item *, Item::PropertyDeclarationMap> &decls)
- : m_productName(productName), m_productItem(productItem), m_parameterDeclarations(decls)
- {
- }
-
- void operator()(const QVariantMap &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());
- }
-
- auto decls = m_parameterDeclarations.value(rootPrototype(m->item));
-
- if (!decls.contains(it.key())) {
- const QualifiedId fullName = QualifiedId(moduleName) << it.key();
- throw ErrorInfo(Tr::tr("Parameter '%1' is not declared.")
- .arg(fullName.toString()), m_productItem->location());
- }
- }
- }
- }
-
- bool moduleExists(const QualifiedId &name) const
- {
- const auto &deps = m_productItem->modules();
- return any_of(deps, [&name](const Item::Module &module) {
- return module.name == name;
- });
- }
-
- const QString &m_productName;
- const Item *m_productItem;
- const QHash<const Item *, Item::PropertyDeclarationMap> &m_parameterDeclarations;
-};
-
-void ModuleLoader::checkDependencyParameterDeclarations(const ProductContext *productContext) const
-{
- DependencyParameterDeclarationCheck dpdc(productContext->name, productContext->item,
- m_parameterDeclarations);
- for (const Item::Module &dep : productContext->item->modules()) {
- if (!dep.parameters.empty())
- dpdc(dep.parameters);
- }
-}
-
-void ModuleLoader::handleModuleSetupError(ModuleLoader::ProductContext *productContext,
- const Item::Module &module, const ErrorInfo &error)
-{
- if (module.required) {
- handleProductError(error, productContext);
- } else {
- qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString()
- << "found, but not usable in product" << productContext->name
- << error.toString();
- createNonPresentModule(module.name.toString(), QStringLiteral("failed validation"),
- module.item);
- }
-}
-
-void ModuleLoader::initProductProperties(const ProductContext &product)
-{
- QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name,
- product.multiplexConfigurationId);
- buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir);
- product.item->setProperty(StringConstants::buildDirectoryProperty(),
- VariantValue::create(buildDir));
- const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath();
- product.item->setProperty(StringConstants::sourceDirectoryProperty(),
- VariantValue::create(sourceDir));
-}
-
-void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths)
-{
- qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath();
-
- Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject);
- if (!checkItemCondition(projectItem))
- return;
- if (propertiesItem) {
- propertiesItem->setScope(projectItem);
- if (!checkItemCondition(propertiesItem))
- return;
- }
-
- Item *loadedItem;
- QString subProjectFilePath;
- try {
- const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath());
- const QString relativeFilePath
- = m_evaluator->stringValue(projectItem, StringConstants::filePathProperty());
- subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath);
- if (referencedFilePaths.contains(subProjectFilePath))
- throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.")
- .arg(relativeFilePath), projectItem->location());
- loadedItem = loadItemFromFile(subProjectFilePath, projectItem->location());
- } catch (const ErrorInfo &error) {
- if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict)
- throw;
- m_logger.printWarning(error);
- return;
- }
-
- loadedItem = wrapInProjectIfNecessary(loadedItem);
- const bool inheritProperties = m_evaluator->boolValue(
- projectItem, StringConstants::inheritPropertiesProperty());
-
- if (inheritProperties)
- copyProperties(projectItem->parent(), loadedItem);
- if (propertiesItem) {
- const Item::PropertyMap &overriddenProperties = propertiesItem->properties();
- for (Item::PropertyMap::ConstIterator it = overriddenProperties.constBegin();
- it != overriddenProperties.constEnd(); ++it) {
- loadedItem->setProperty(it.key(), it.value());
- }
- }
-
- Item::addChild(projectItem, loadedItem);
- projectItem->setScope(projectContext->scope);
- handleProject(projectContext->result, projectContext->topLevelProject, loadedItem,
- Set<QString>(referencedFilePaths) << subProjectFilePath);
-}
-
-QList<Item *> ModuleLoader::loadReferencedFile(const QString &relativePath,
- const CodeLocation &referencingLocation,
- const Set<QString> &referencedFilePaths,
- ModuleLoader::ProductContext &dummyContext)
-{
- QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()),
- relativePath);
- if (FileInfo(absReferencePath).isDir()) {
- QString qbsFilePath;
-
- QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards());
- while (dit.hasNext()) {
- if (!qbsFilePath.isEmpty()) {
- throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one "
- "qbs file.").arg(absReferencePath), referencingLocation);
- }
- qbsFilePath = dit.next();
- }
- if (qbsFilePath.isEmpty()) {
- throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.")
- .arg(absReferencePath), referencingLocation);
- }
- absReferencePath = qbsFilePath;
- }
- if (referencedFilePaths.contains(absReferencePath))
- throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath),
- referencingLocation);
- Item * const subItem = loadItemFromFile(absReferencePath, referencingLocation);
- if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) {
- ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.")
- .arg(subItem->typeName()));
- error.append(Tr::tr("Item is defined here."), subItem->location());
- error.append(Tr::tr("File is referenced here."), referencingLocation);
- throw error;
- }
- subItem->setScope(dummyContext.project->scope);
- subItem->setParent(dummyContext.project->item);
- QList<Item *> loadedItems;
- loadedItems << subItem;
- if (subItem->type() == ItemType::Product) {
- handleProfileItems(subItem, dummyContext.project);
- loadedItems << multiplexProductItem(&dummyContext, subItem);
- }
- return loadedItems;
-}
-
-void ModuleLoader::handleGroup(ProductContext *productContext, Item *groupItem,
- const ModuleDependencies &reverseDepencencies)
-{
- checkCancelation();
- propagateModulesFromParent(productContext, groupItem, reverseDepencencies);
- checkItemCondition(groupItem);
- for (Item * const child : groupItem->children()) {
- if (child->type() == ItemType::Group)
- handleGroup(productContext, child, reverseDepencencies);
- }
-}
-
-void ModuleLoader::handleAllPropertyOptionsItems(Item *item)
-{
- QList<Item *> childItems = item->children();
- auto childIt = childItems.begin();
- while (childIt != childItems.end()) {
- Item * const child = *childIt;
- if (child->type() == ItemType::PropertyOptions) {
- handlePropertyOptions(child);
- childIt = childItems.erase(childIt);
- } else {
- handleAllPropertyOptionsItems(child);
- ++childIt;
- }
- }
- item->setChildren(childItems);
-}
-
-void ModuleLoader::handlePropertyOptions(Item *optionsItem)
-{
- const QString name = m_evaluator->stringValue(optionsItem, StringConstants::nameProperty());
- if (name.isEmpty()) {
- throw ErrorInfo(Tr::tr("PropertyOptions item needs a name property"),
- optionsItem->location());
- }
- const QString description = m_evaluator->stringValue(
- optionsItem, StringConstants::descriptionProperty());
- const auto removalVersion = Version::fromString(m_evaluator->stringValue(optionsItem,
- StringConstants::removalVersionProperty()));
- const auto allowedValues = m_evaluator->stringListValue(
- optionsItem, StringConstants::allowedValuesProperty());
-
- PropertyDeclaration decl = optionsItem->parent()->propertyDeclaration(name);
- if (!decl.isValid()) {
- decl.setName(name);
- decl.setType(PropertyDeclaration::Variant);
- }
- decl.setDescription(description);
- if (removalVersion.isValid()) {
- DeprecationInfo di(removalVersion, description);
- decl.setDeprecationInfo(di);
- }
- decl.setAllowedValues(allowedValues);
- const ValuePtr property = optionsItem->parent()->property(name);
- if (!property && !decl.isExpired()) {
- throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'")
- .arg(name), optionsItem->location());
- }
- if (property && decl.isExpired()) {
- ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but "
- "is still present.")
- .arg(name, removalVersion.toString()),
- property->location());
- e.append(Tr::tr("Removal version for '%1' specified here.").arg(name),
- optionsItem->location());
- m_logger.printWarning(e);
- }
- optionsItem->parent()->setPropertyDeclaration(name, decl);
-}
-
-static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
-{
- if (value->type() == Value::ItemValueType) {
- const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value);
- const Item * const valueItem = itemValue->item();
- Item * const subItem = dst->itemProperty(name, itemValue)->item();
- for (QMap<QString, ValuePtr>::const_iterator it = valueItem->properties().constBegin();
- it != valueItem->properties().constEnd(); ++it)
- mergeProperty(subItem, it.key(), it.value());
- } else {
- // If the property already exists set up the base value.
- if (value->type() == Value::JSSourceValueType) {
- const auto jsValue = static_cast<JSSourceValue *>(value.get());
- if (jsValue->isBuiltinDefaultValue())
- return;
- const ValuePtr baseValue = dst->property(name);
- if (baseValue) {
- QBS_CHECK(baseValue->type() == Value::JSSourceValueType);
- const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>(
- baseValue->clone());
- jsValue->setBaseValue(jsBaseValue);
- std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives();
- jsValue->clearAlternatives();
- for (JSSourceValue::Alternative &a : alternatives) {
- a.value->setBaseValue(jsBaseValue);
- jsValue->addAlternative(a);
- }
- }
- }
- dst->setProperty(name, value);
- }
-}
-
-bool ModuleLoader::checkExportItemCondition(Item *exportItem, const ProductContext &productContext)
-{
- class ScopeHandler {
- public:
- ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem)
- : m_exportItem(exportItem)
- {
- if (!*cachedScopeItem)
- *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope);
- Item * const scope = *cachedScopeItem;
- QBS_CHECK(productContext.item->file());
- scope->setFile(productContext.item->file());
- scope->setScope(productContext.item);
- productContext.project->scope->copyProperty(StringConstants::projectVar(), scope);
- productContext.scope->copyProperty(StringConstants::productVar(), scope);
- QBS_CHECK(!exportItem->scope());
- exportItem->setScope(scope);
- }
- ~ScopeHandler() { m_exportItem->setScope(nullptr); }
-
- private:
- Item * const m_exportItem;
- } scopeHandler(exportItem, productContext, &m_tempScopeItem);
- return checkItemCondition(exportItem);
-}
-
-void ModuleLoader::printProfilingInfo()
-{
- if (!m_parameters.logElapsedTime())
- return;
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Project file loading and parsing took %1.")
- .arg(elapsedTimeString(m_reader->elapsedTime()));
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Preparing products took %1.")
- .arg(elapsedTimeString(m_elapsedTimePrepareProducts));
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Setting up product dependencies took %1.")
- .arg(elapsedTimeString(m_elapsedTimeProductDependencies));
- m_logger.qbsLog(LoggerInfo, true) << "\t\t"
- << Tr::tr("Running module providers took %1.")
- .arg(elapsedTimeString(m_elapsedTimeModuleProviders));
- m_logger.qbsLog(LoggerInfo, true) << "\t\t"
- << Tr::tr("Setting up transitive product dependencies took %1.")
- .arg(elapsedTimeString(m_elapsedTimeTransitiveDependencies));
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Handling products took %1.")
- .arg(elapsedTimeString(m_elapsedTimeHandleProducts));
- m_probesResolver->printProfilingInfo();
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Property checking took %1.")
- .arg(elapsedTimeString(m_elapsedTimePropertyChecking));
-}
-
-static void mergeParameters(QVariantMap &dst, const QVariantMap &src)
-{
- for (auto it = src.begin(); it != src.end(); ++it) {
- if (it.value().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();
- }
- }
-}
-
-static void adjustParametersScopes(Item *item, Item *scope)
-{
- if (item->type() == ItemType::ModuleParameters) {
- item->setScope(scope);
- return;
- }
-
- for (const auto &value : item->properties()) {
- if (value->type() != Value::ItemValueType)
- continue;
- adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope);
- }
-}
-
-bool ModuleLoader::mergeExportItems(const ProductContext &productContext)
-{
- std::vector<Item *> exportItems;
- QList<Item *> children = productContext.item->children();
-
- auto isExport = [](Item *item) { return item->type() == ItemType::Export; };
- std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport);
- qbs::Internal::removeIf(children, isExport);
-
- // Note that we do not return if there are no Export items: The "merged" item becomes the
- // "product module", which always needs to exist, regardless of whether the product sources
- // actually contain an Export item or not.
- if (!exportItems.empty())
- productContext.item->setChildren(children);
-
- Item *merged = Item::create(productContext.item->pool(), ItemType::Export);
- const QString &nameKey = StringConstants::nameProperty();
- const ValuePtr nameValue = VariantValue::create(productContext.name);
- merged->setProperty(nameKey, nameValue);
- Set<FileContextConstPtr> filesWithExportItem;
- ProductModuleInfo pmi;
- bool hasDependenciesOnProductType = false;
- for (Item * const exportItem : qAsConst(exportItems)) {
- checkCancelation();
- if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file())))
- throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."),
- exportItem->location());
- exportItem->setProperty(nameKey, nameValue);
- if (!checkExportItemCondition(exportItem, productContext))
- continue;
- filesWithExportItem += exportItem->file();
- for (Item * const child : exportItem->children()) {
- if (child->type() == ItemType::Parameters) {
- adjustParametersScopes(child, child);
- mergeParameters(pmi.defaultParameters, getJsVariant(
- m_evaluator->engine()->context(), m_evaluator->scriptValue(child)).toMap());
- } else {
- if (child->type() == ItemType::Depends) {
- bool productTypesIsSet;
- m_evaluator->stringValue(child, StringConstants::productTypesProperty(),
- QString(), &productTypesIsSet);
- if (productTypesIsSet)
- hasDependenciesOnProductType = true;
- }
- Item::addChild(merged, child);
- }
- }
- const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations();
- for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) {
- const PropertyDeclaration &newDecl = it.value();
- const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key());
- if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) {
- ErrorInfo error(Tr::tr("Export item in inherited item redeclares property "
- "'%1' with different type.").arg(it.key()), exportItem->location());
- handlePropertyError(error, m_parameters, m_logger);
- }
- merged->setPropertyDeclaration(newDecl.name(), newDecl);
- }
- for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin();
- it != exportItem->properties().constEnd(); ++it) {
- mergeProperty(merged, it.key(), it.value());
- }
- }
- merged->setFile(exportItems.empty()
- ? productContext.item->file() : exportItems.back()->file());
- merged->setLocation(exportItems.empty()
- ? productContext.item->location() : exportItems.back()->location());
- Item::addChild(productContext.item, merged);
- merged->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- pmi.exportItem = merged;
- pmi.multiplexId = productContext.multiplexConfigurationId;
- productContext.project->topLevelProject->productModules.insert(productContext.name, pmi);
- if (hasDependenciesOnProductType)
- m_exportsWithDeferredDependsItems.insert(merged);
- return !exportItems.empty();
-}
-
-Item *ModuleLoader::loadItemFromFile(const QString &filePath,
- const CodeLocation &referencingLocation)
-{
- Item *item = m_reader->readFile(filePath, referencingLocation);
- handleAllPropertyOptionsItems(item);
- return item;
-}
-
-void ModuleLoader::handleProfileItems(Item *item, ProjectContext *projectContext)
-{
- const std::vector<Item *> profileItems = collectProfileItems(item, projectContext);
- for (Item * const profileItem : profileItems) {
- try {
- handleProfile(profileItem);
- } catch (const ErrorInfo &e) {
- handlePropertyError(e, m_parameters, m_logger);
- }
- }
-}
-
-std::vector<Item *> ModuleLoader::collectProfileItems(Item *item, ProjectContext *projectContext)
-{
- QList<Item *> childItems = item->children();
- std::vector<Item *> profileItems;
- Item * scope = item->type() == ItemType::Project ? projectContext->scope : nullptr;
- for (auto it = childItems.begin(); it != childItems.end();) {
- Item * const childItem = *it;
- if (childItem->type() == ItemType::Profile) {
- if (!scope) {
- const ItemValuePtr itemValue = ItemValue::create(item);
- scope = Item::create(m_pool, ItemType::Scope);
- scope->setProperty(StringConstants::productVar(), itemValue);
- scope->setFile(item->file());
- scope->setScope(projectContext->scope);
- }
- childItem->setScope(scope);
- profileItems.push_back(childItem);
- it = childItems.erase(it);
- } else {
- if (childItem->type() == ItemType::Product) {
- for (Item * const profileItem : collectProfileItems(childItem, projectContext))
- profileItems.push_back(profileItem);
- }
- ++it;
- }
- }
- if (!profileItems.empty())
- item->setChildren(childItems);
- return profileItems;
-}
-
-void ModuleLoader::evaluateProfileValues(const QualifiedId &namePrefix, Item *item,
- Item *profileItem, QVariantMap &values)
-{
- const Item::PropertyMap &props = item->properties();
- for (auto it = props.begin(); it != props.end(); ++it) {
- QualifiedId name = namePrefix;
- name << it.key();
- switch (it.value()->type()) {
- case Value::ItemValueType:
- evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(),
- profileItem, values);
- break;
- case Value::VariantValueType:
- values.insert(name.join(QLatin1Char('.')),
- std::static_pointer_cast<VariantValue>(it.value())->value());
- break;
- case Value::JSSourceValueType:
- item->setType(ItemType::ModulePrefix); // TODO: Introduce new item type
- if (item != profileItem)
- item->setScope(profileItem);
- const ScopedJsValue sv(m_evaluator->engine()->context(),
- m_evaluator->value(item, it.key()));
- values.insert(name.join(QLatin1Char('.')),
- getJsVariant(m_evaluator->engine()->context(), sv));
- break;
- }
- }
-}
-
-void ModuleLoader::handleProfile(Item *profileItem)
-{
- QVariantMap values;
- evaluateProfileValues(QualifiedId(), profileItem, profileItem, values);
- const bool condition = values.take(StringConstants::conditionProperty()).toBool();
- if (!condition)
- return;
- const QString profileName = values.take(StringConstants::nameProperty()).toString();
- if (profileName.isEmpty())
- throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location());
- if (profileName == Profile::fallbackName()) {
- throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.")
- .arg(profileName), profileItem->location());
- }
- if (m_localProfiles.contains(profileName)) {
- throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName),
- profileItem->location());
- }
- m_localProfiles.insert(profileName, values);
-}
-
-void ModuleLoader::collectNameFromOverride(const QString &overrideString)
-{
- static const auto extract = [](const QString &prefix, const QString &overrideString) {
- if (!overrideString.startsWith(prefix))
- return QString();
- const int startPos = prefix.length();
- const int endPos = overrideString.lastIndexOf(StringConstants::dot());
- if (endPos == -1)
- return QString();
- return overrideString.mid(startPos, endPos - startPos);
- };
- const QString &projectName = extract(StringConstants::projectsOverridePrefix(), overrideString);
- if (!projectName.isEmpty()) {
- m_projectNamesUsedInOverrides.insert(projectName);
- return;
- }
- const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString);
- if (!productName.isEmpty()) {
- m_productNamesUsedInOverrides.insert(productName.left(
- productName.indexOf(StringConstants::dot())));
- return;
- }
-}
-
-void ModuleLoader::checkProjectNamesInOverrides(const ModuleLoader::TopLevelProjectContext &tlp)
-{
- for (const QString &projectNameInOverride : m_projectNamesUsedInOverrides) {
- if (m_disabledProjects.contains(projectNameInOverride))
- continue;
- bool found = false;
- for (const ProjectContext * const p : tlp.projects) {
- if (p->name == projectNameInOverride) {
- found = true;
- break;
- }
- }
- if (!found) {
- handlePropertyError(Tr::tr("Unknown project '%1' in property override.")
- .arg(projectNameInOverride), m_parameters, m_logger);
- }
- }
-}
-
-void ModuleLoader::checkProductNamesInOverrides()
-{
- for (const QString &productNameInOverride : m_productNamesUsedInOverrides) {
- if (m_erroneousProducts.contains(productNameInOverride))
- continue;
- bool found = false;
- for (const auto &kv : m_productsByName) {
- // In an override string such as "a.b.c:d, we cannot tell whether we have a product
- // "a" and a module "b.c" or a product "a.b" and a module "c", so we need to take
- // care not to emit false positives here.
- if (kv.first == productNameInOverride
- || kv.first.startsWith(productNameInOverride + StringConstants::dot())) {
- found = true;
- break;
- }
- }
- if (!found) {
- handlePropertyError(Tr::tr("Unknown product '%1' in property override.")
- .arg(productNameInOverride), m_parameters, m_logger);
- }
- }
-}
-
-void ModuleLoader::setSearchPathsForProduct(ModuleLoader::ProductContext *product)
-{
- product->searchPaths = readExtraSearchPaths(product->item);
- Settings settings(m_parameters.settingsDirectory());
- const QVariantMap profileContents = product->project->result->profileConfigs
- .value(product->profileName).toMap();
- const QStringList prefsSearchPaths = Preferences(&settings, profileContents).searchPaths();
- const QStringList &currentSearchPaths = m_reader->allSearchPaths();
- for (const QString &p : prefsSearchPaths) {
- if (!currentSearchPaths.contains(p) && FileInfo(p).exists())
- product->searchPaths << p;
- }
-}
-
-ModuleLoader::ShadowProductInfo ModuleLoader::getShadowProductInfo(
- const ModuleLoader::ProductContext &product) const
-{
- const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix());
- return std::make_pair(isShadowProduct, isShadowProduct
- ? product.name.mid(StringConstants::shadowProductPrefix().size())
- : QString());
-}
-
-void ModuleLoader::collectProductsByName(const TopLevelProjectContext &topLevelProject)
-{
- for (ProjectContext * const project : topLevelProject.projects) {
- for (ProductContext &product : project->products)
- m_productsByName.insert({ product.name, &product });
- }
-}
-
-void ModuleLoader::collectProductsByType(const ModuleLoader::TopLevelProjectContext &topLevelProject)
-{
- for (ProjectContext * const project : topLevelProject.projects) {
- for (ProductContext &product : project->products) {
- try {
- const FileTags productTags
- = m_evaluator->fileTagsValue(product.item, StringConstants::typeProperty());
- for (const FileTag &tag : productTags)
- m_productsByType.insert({ tag, &product});
- } catch (const ErrorInfo &) {
- qCDebug(lcModuleLoader) << "product" << product.name << "has complex type "
- " and won't get an entry in the type map";
- }
- }
- }
-}
-
-void ModuleLoader::propagateModulesFromParent(ProductContext *productContext, Item *groupItem,
- const ModuleDependencies &reverseDepencencies)
-{
- QBS_CHECK(groupItem->type() == ItemType::Group);
- QHash<QualifiedId, Item *> moduleInstancesForGroup;
-
- // Step 1: Instantiate the product's modules for the group.
- for (Item::Module m : groupItem->parent()->modules()) {
- Item *targetItem = moduleInstanceItem(groupItem, m.name);
- targetItem->setPrototype(m.item);
-
- Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope);
- moduleScope->setFile(groupItem->file());
- moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids
- moduleScope->setScope(groupItem);
- targetItem->setScope(moduleScope);
-
- targetItem->setFile(m.item->file());
-
- // "parent" should point to the group/artifact parent
- targetItem->setParent(groupItem->parent());
-
- targetItem->setOuterItem(m.item);
-
- m.item = targetItem;
- groupItem->addModule(m);
- moduleInstancesForGroup.insert(m.name, targetItem);
- }
-
- // Step 2: Make the inter-module references point to the instances created in step 1.
- for (const Item::Module &module : groupItem->modules()) {
- Item::Modules adaptedModules;
- const Item::Modules &oldModules = module.item->prototype()->modules();
- for (Item::Module depMod : oldModules) {
- depMod.item = moduleInstancesForGroup.value(depMod.name);
- adaptedModules << depMod;
- if (depMod.name.front() == module.name.front())
- continue;
- const ItemValuePtr &modulePrefix = groupItem->itemProperty(depMod.name.front());
- QBS_CHECK(modulePrefix);
- module.item->setProperty(depMod.name.front(), modulePrefix);
- }
- module.item->setModules(adaptedModules);
- }
-
- const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(groupItem);
- if (propsSetInGroup.empty())
- return;
- productContext->info.modulePropertiesSetInGroups
- .insert(std::make_pair(groupItem, propsSetInGroup));
-
- // Step 3: Adapt defining items in values. This is potentially necessary if module properties
- // get assigned on the group level.
- for (const Item::Module &module : groupItem->modules()) {
- const QualifiedIdSet &dependents = reverseDepencencies.value(module.name);
- Item::Modules dependentModules;
- dependentModules.reserve(int(dependents.size()));
- for (const QualifiedId &depName : dependents) {
- Item * const itemOfDependent = moduleInstancesForGroup.value(depName);
- QBS_CHECK(itemOfDependent);
- Item::Module depMod;
- depMod.name = depName;
- depMod.item = itemOfDependent;
- dependentModules << depMod;
- }
- adjustDefiningItemsInGroupModuleInstances(module, dependentModules);
- }
-}
-
-static Item *createReplacementForDefiningItem(const Item *definingItem, ItemType type)
-{
- Item *replacement = Item::create(definingItem->pool(), type);
- replacement->setLocation(definingItem->location());
- definingItem->copyProperty(StringConstants::nameProperty(), replacement);
- return replacement;
-}
-
-void ModuleLoader::adjustDefiningItemsInGroupModuleInstances(const Item::Module &module,
- const Item::Modules &dependentModules)
-{
- if (!module.item->isPresentModule())
- return;
-
- // There are three cases:
- // a) The defining item is the "main" module instance, i.e. the one instantiated in the
- // product directly (or a parent group).
- // b) The defining item refers to the module prototype (or the replacement of it
- // created in the module merger [for products] or in this function [for parent groups]).
- // c) The defining item is a different instance of the module, i.e. it was instantiated
- // in some other module.
-
- std::unordered_map<Item *, Item *> definingItemReplacements;
-
- Item *modulePrototype = rootPrototype(module.item->prototype());
- QBS_CHECK(modulePrototype->type() == ItemType::Module
- || modulePrototype->type() == ItemType::Export);
-
- const Item::PropertyDeclarationMap &propDecls = modulePrototype->propertyDeclarations();
- for (const auto &decl : propDecls) {
- const QString &propName = decl.name();
-
- // Module properties assigned in the group are not relevant here, as nothing
- // gets inherited in that case. In particular, setting a list property
- // overwrites the value from the product's (or parent group's) instance completely,
- // rather than appending to it (concatenation happens via outer.concat()).
- ValueConstPtr propValue = module.item->ownProperty(propName);
- if (propValue)
- continue;
-
- // Find the nearest prototype instance that has the value assigned.
- // The result is either an instance of a parent group (or the parent group's
- // parent group and so on) or the instance of the product or the module prototype.
- // In the latter case, we don't have to do anything.
- const Item *instanceWithProperty = module.item;
- int prototypeChainLen = 0;
- do {
- instanceWithProperty = instanceWithProperty->prototype();
- QBS_CHECK(instanceWithProperty);
- ++prototypeChainLen;
- propValue = instanceWithProperty->ownProperty(propName);
- } while (!propValue);
- QBS_CHECK(propValue);
-
- if (propValue->type() != Value::JSSourceValueType)
- continue;
-
- bool hasDefiningItem = false;
- for (ValueConstPtr v = propValue; v && !hasDefiningItem; v = v->next())
- hasDefiningItem = v->definingItem();
- if (!hasDefiningItem)
- continue;
-
- const ValuePtr clonedValue = propValue->clone();
- for (ValuePtr v = clonedValue; v; v = v->next()) {
- QBS_CHECK(v->definingItem());
-
- Item *& replacement = definingItemReplacements[v->definingItem()];
- static const QString caseA = QStringLiteral("__group_case_a");
- if (v->definingItem() == instanceWithProperty
- || v->definingItem()->variantProperty(caseA)) {
- // Case a)
- // For values whose defining item is the product's (or parent group's) instance,
- // we take its scope and replace references to module instances with those from the
- // group's instance. This handles cases like the following:
- // Product {
- // name: "theProduct"
- // aModule.listProp: [name, otherModule.stringProp]
- // Group { name: "theGroup"; otherModule.stringProp: name }
- // ...
- // }
- // In the above example, aModule.listProp is set to ["theProduct", "theGroup"]
- // (plus potential values from the prototype and other module instances,
- // which are different Value objects in the "next chain").
- if (!replacement) {
- replacement = createReplacementForDefiningItem(v->definingItem(),
- v->definingItem()->type());
- Item * const scope = Item::create(v->definingItem()->pool(), ItemType::Scope);
- scope->setProperties(module.item->scope()->properties());
- Item * const scopeScope
- = Item::create(v->definingItem()->pool(), ItemType::Scope);
- scopeScope->setProperties(v->definingItem()->scope()->scope()->properties());
- scope->setScope(scopeScope);
- replacement->setScope(scope);
- const Item::PropertyMap &groupScopeProperties
- = module.item->scope()->scope()->properties();
- for (auto propIt = groupScopeProperties.begin();
- propIt != groupScopeProperties.end(); ++propIt) {
- if (propIt.value()->type() == Value::ItemValueType)
- scopeScope->setProperty(propIt.key(), propIt.value());
- }
- }
- replacement->setPropertyDeclaration(propName, decl);
- replacement->setProperty(propName, v);
- replacement->setProperty(caseA, VariantValue::invalidValue());
- } else if (v->definingItem()->type() == ItemType::Module) {
- // Case b)
- // For values whose defining item is the module prototype, we change the scope to
- // the group's instance, analogous to what we do in
- // ModuleMerger::appendPrototypeValueToNextChain().
- QBS_CHECK(!decl.isScalar());
- QBS_CHECK(!v->next());
- Item *& replacement = definingItemReplacements[v->definingItem()];
- if (!replacement) {
- replacement = createReplacementForDefiningItem(v->definingItem(),
- ItemType::Module);
- replacement->setScope(module.item);
- }
- QBS_CHECK(!replacement->hasOwnProperty(caseA));
- qCDebug(lcModuleLoader).noquote().nospace()
- << "replacing defining item for prototype; module is "
- << module.name.toString() << module.item
- << ", property is " << propName
- << ", old defining item was " << v->definingItem()
- << " with scope" << v->definingItem()->scope()
- << ", new defining item is" << replacement
- << " with scope" << replacement->scope();
- if (v->type() == Value::JSSourceValueType) {
- qCDebug(lcModuleLoader) << "value source code is"
- << std::static_pointer_cast<JSSourceValue>(v)->sourceCode().toString();
- }
- replacement->setPropertyDeclaration(propName, decl);
- replacement->setProperty(propName, v);
- } else {
- // Look for instance scopes of other module instances in defining items and
- // replace the affected values.
- // This is case c) as introduced above. See ModuleMerger::replaceItemInScopes()
- // for a detailed explanation.
-
- QBS_CHECK(v->definingItem()->scope() && v->definingItem()->scope()->scope());
- bool found = false;
- for (const Item::Module &depMod : dependentModules) {
- const Item *depModPrototype = depMod.item->prototype();
- for (int i = 1; i < prototypeChainLen; ++i)
- depModPrototype = depModPrototype->prototype();
- if (v->definingItem()->scope()->scope() != depModPrototype)
- continue;
-
- found = true;
- Item *& replacement = definingItemReplacements[v->definingItem()];
- if (!replacement) {
- replacement = createReplacementForDefiningItem(v->definingItem(),
- v->definingItem()->type());
- replacement->setProperties(v->definingItem()->properties());
- for (const auto &decl : v->definingItem()->propertyDeclarations())
- replacement->setPropertyDeclaration(decl.name(), decl);
- replacement->setPrototype(v->definingItem()->prototype());
- replacement->setScope(Item::create(v->definingItem()->pool(),
- ItemType::Scope));
- replacement->scope()->setScope(depMod.item);
- }
- QBS_CHECK(!replacement->hasOwnProperty(caseA));
- qCDebug(lcModuleLoader) << "reset instance scope of module"
- << depMod.name.toString() << "in property"
- << propName << "of module" << module.name;
- }
- QBS_CHECK(found);
- }
- QBS_CHECK(replacement);
- v->setDefiningItem(replacement);
- }
- module.item->setProperty(propName, clonedValue);
- }
-}
-
-void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *item,
- ProductContext *productContext)
-{
- QBS_CHECK(m_dependencyResolvingPass == 1 || m_dependencyResolvingPass == 2);
-
- if (!productContext || m_dependencyResolvingPass == 1) {
- const Item::Module baseModule = loadBaseModule(dependsContext->product, item);
- item->addModule(baseModule);
- }
-
- // Resolve all Depends items.
- ItemModuleList loadedModules;
- QList<Item *> dependsItemPerLoadedModule;
- ProductDependencies productDependencies;
- const auto handleDependsItem = [&](Item *child) {
- if (child->type() != ItemType::Depends)
- return;
-
- int lastModulesCount = loadedModules.size();
- try {
- resolveDependsItem(dependsContext, child->parent(), child, &loadedModules,
- &productDependencies);
- } catch (const ErrorInfo &e) {
- if (!productContext)
- throw;
- handleProductError(e, productContext);
- }
- for (int i = lastModulesCount; i < loadedModules.size(); ++i)
- dependsItemPerLoadedModule.push_back(child);
-
- };
- if (productContext && m_dependencyResolvingPass == 2) {
- for (const auto &deferData : productContext->deferredDependsItems) {
- dependsContext->exportingProductItem = deferData.first;
- for (Item * const dependsItem : deferData.second)
- handleDependsItem(dependsItem);
- }
- } else {
- for (Item * const child : item->children())
- handleDependsItem(child);
- }
- QBS_CHECK(loadedModules.size() == dependsItemPerLoadedModule.size());
-
- Item *lastDependsItem = nullptr;
- for (Item * const dependsItem : dependsItemPerLoadedModule) {
- if (dependsItem == lastDependsItem)
- continue;
- adjustParametersScopes(dependsItem, dependsItem);
- forwardParameterDeclarations(dependsItem, loadedModules);
- lastDependsItem = dependsItem;
- }
-
- for (int i = 0; i < loadedModules.size(); ++i) {
- Item::Module &module = loadedModules[i];
- mergeParameters(module.parameters, extractParameters(dependsItemPerLoadedModule.at(i)));
- item->addModule(module);
-
- const QString moduleName = module.name.toString();
- std::for_each(productDependencies.begin(), productDependencies.end(),
- [&module, &moduleName] (ModuleLoaderResult::ProductInfo::Dependency &dep) {
- if (dep.name == moduleName)
- dep.parameters = module.parameters;
- });
- }
-
- dependsContext->productDependencies->insert(
- dependsContext->productDependencies->end(),
- productDependencies.cbegin(), productDependencies.cend());
-}
-
-class RequiredChainManager
-{
-public:
- RequiredChainManager(std::vector<bool> &requiredChain, bool required)
- : m_requiredChain(requiredChain)
- {
- m_requiredChain.push_back(required);
- }
-
- ~RequiredChainManager() { m_requiredChain.pop_back(); }
-
-private:
- std::vector<bool> &m_requiredChain;
-};
-
-void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *parentItem,
- Item *dependsItem, ItemModuleList *moduleResults,
- ProductDependencies *productResults)
-{
- checkCancelation();
- if (!checkItemCondition(dependsItem)) {
- qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
- return;
- }
- bool nameIsSet;
- const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(),
- QString(), &nameIsSet);
- bool submodulesPropertySet;
- const QStringList submodules = m_evaluator->stringListValue(
- dependsItem, StringConstants::submodulesProperty(), &submodulesPropertySet);
- if (submodules.empty() && submodulesPropertySet) {
- qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list.";
- return;
- }
- if (Q_UNLIKELY(submodules.size() > 1 && !dependsItem->id().isEmpty())) {
- QString msg = Tr::tr("A Depends item with more than one module cannot have an id.");
- throw ErrorInfo(msg, dependsItem->location());
- }
- const FallbackMode fallbackMode = m_parameters.fallbackProviderEnabled()
- && m_evaluator->boolValue(dependsItem, StringConstants::enableFallbackProperty())
- ? FallbackMode::Enabled : FallbackMode::Disabled;
-
- QList<QualifiedId> moduleNames;
- const QualifiedId nameParts = QualifiedId::fromString(name);
- if (submodules.empty()) {
- // Ignore explicit dependencies on the base module, which has already been loaded.
- if (name == StringConstants::qbsModule())
- return;
-
- moduleNames << nameParts;
- } else {
- for (const QString &submodule : submodules)
- moduleNames << nameParts + QualifiedId::fromString(submodule);
- }
-
- Item::Module result;
- bool productTypesIsSet;
- m_evaluator->stringValue(dependsItem, StringConstants::productTypesProperty(),
- QString(), &productTypesIsSet);
- if (m_dependencyResolvingPass == 1 && productTypesIsSet) {
- qCDebug(lcModuleLoader) << "queuing product" << dependsContext->product->name
- << "for a second dependencies resolving pass";
- m_productsWithDeferredDependsItems[dependsContext->product].insert(
- DeferredDependsContext(dependsContext->exportingProductItem, parentItem));
- return;
- }
-
- const bool isRequiredValue =
- m_evaluator->boolValue(dependsItem, StringConstants::requiredProperty());
- const bool isRequired = !productTypesIsSet
- && isRequiredValue
- && !contains(m_requiredChain, false);
- const Version minVersion = Version::fromString(
- m_evaluator->stringValue(dependsItem,
- StringConstants::versionAtLeastProperty()));
- const Version maxVersion = Version::fromString(
- m_evaluator->stringValue(dependsItem, StringConstants::versionBelowProperty()));
- const VersionRange versionRange(minVersion, maxVersion);
- QStringList multiplexConfigurationIds = m_evaluator->stringListValue(
- dependsItem,
- StringConstants::multiplexConfigurationIdsProperty());
- if (multiplexConfigurationIds.empty())
- multiplexConfigurationIds << QString();
-
- for (const QualifiedId &moduleName : qAsConst(moduleNames)) {
- // Don't load the same module twice. Duplicate Depends statements can easily
- // happen due to inheritance.
- const auto it = std::find_if(moduleResults->begin(), moduleResults->end(),
- [moduleName](const Item::Module &m) { return m.name == moduleName; });
- if (it != moduleResults->end()) {
- it->required = it->required || isRequired;
- it->requiredValue = it->requiredValue || isRequiredValue;
- it->fallbackEnabled = it->fallbackEnabled && fallbackMode == FallbackMode::Enabled;
- it->versionRange.narrowDown(versionRange);
- continue;
- }
-
- QVariantMap defaultParameters;
- Item *moduleItem = loadModule(dependsContext->product, dependsContext->exportingProductItem,
- parentItem, dependsItem->location(), dependsItem->id(),
- moduleName, multiplexConfigurationIds.first(), fallbackMode,
- isRequired, &result.isProduct, &defaultParameters);
- if (!moduleItem) {
- const QString productName = ResolvedProduct::fullDisplayName(
- dependsContext->product->name,
- dependsContext->product->multiplexConfigurationId);
- if (!multiplexConfigurationIds.first().isEmpty()) {
- const QString depName = ResolvedProduct::fullDisplayName(
- moduleName.toString(), multiplexConfigurationIds.first());
- throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not "
- "fulfilled.").arg(productName, depName));
- }
- ErrorInfo e(Tr::tr("Dependency '%1' not found for product '%2'.")
- .arg(moduleName.toString(), productName), dependsItem->location());
- throw e;
- }
- if (result.isProduct && !m_dependsChain.empty() && !m_dependsChain.back().isProduct) {
- throw ErrorInfo(Tr::tr("Invalid dependency on product '%1': Modules cannot depend on "
- "products. You may want to turn your module into a product and "
- "add the dependency in that product's Export item.")
- .arg(moduleName.toString()), dependsItem->location());
- }
- qCDebug(lcModuleLoader) << "module loaded:" << moduleName.toString();
- result.name = moduleName;
- result.item = moduleItem;
- result.requiredValue = isRequiredValue;
- result.required = isRequired;
- result.fallbackEnabled = fallbackMode == FallbackMode::Enabled;
- result.parameters = defaultParameters;
- result.versionRange = versionRange;
- moduleResults->push_back(result);
- if (result.isProduct) {
- qCDebug(lcModuleLoader) << "product dependency loaded:" << moduleName.toString();
- bool profilesPropertyWasSet = false;
- QStringList profiles = m_evaluator->stringListValue(dependsItem,
- StringConstants::profilesProperty(),
- &profilesPropertyWasSet);
- if (profiles.empty()) {
- if (profilesPropertyWasSet)
- profiles.push_back(StringConstants::star());
- else
- profiles.push_back(QString());
- }
- for (const QString &profile : qAsConst(profiles)) {
- for (const QString &multiplexId : qAsConst(multiplexConfigurationIds)) {
- ModuleLoaderResult::ProductInfo::Dependency dependency;
- dependency.name = moduleName.toString();
- dependency.profile = profile;
- dependency.multiplexConfigurationId = multiplexId;
- dependency.isRequired = isRequired;
- productResults->push_back(dependency);
- }
- }
- }
- }
-}
-
-void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem,
- const ItemModuleList &modules)
-{
- for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) {
- if (it.value()->type() != Value::ItemValueType)
- continue;
- forwardParameterDeclarations(it.key(),
- std::static_pointer_cast<ItemValue>(it.value())->item(),
- modules);
- }
-}
-
-void ModuleLoader::forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
- const ItemModuleList &modules)
-{
- auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) {
- return m.name == moduleName;
- });
- if (it != modules.end()) {
- item->setPropertyDeclarations(m_parameterDeclarations.value(rootPrototype(it->item)));
- } else {
- for (auto it = item->properties().begin(); it != item->properties().end(); ++it) {
- if (it.value()->type() != Value::ItemValueType)
- continue;
- forwardParameterDeclarations(QualifiedId(moduleName) << it.key(),
- std::static_pointer_cast<ItemValue>(it.value())->item(),
- modules);
- }
- }
-}
-
-void ModuleLoader::resolveParameterDeclarations(const Item *module)
-{
- Item::PropertyDeclarationMap decls;
- const auto &moduleChildren = module->children();
- for (Item *param : moduleChildren) {
- if (param->type() != ItemType::Parameter)
- continue;
- const auto &paramDecls = param->propertyDeclarations();
- for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it)
- decls.insert(it.key(), it.value());
- }
- m_parameterDeclarations.insert(module, decls);
-}
-
-static bool isItemValue(const ValuePtr &v)
-{
- return v->type() == Value::ItemValueType;
-}
-
-static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties)
-{
- Item::PropertyMap result;
- auto itEnd = properties.end();
- for (auto it = properties.begin(); it != itEnd; ++it) {
- if (isItemValue(it.value()))
- result.insert(it.key(), it.value());
- }
- return result;
-}
-
-static QVariantMap safeToVariant(JSContext *ctx, const JSValue &v)
-{
- QVariantMap result;
- handleJsProperties(ctx, v, [&](const JSAtom &prop, const JSPropertyDescriptor &desc) {
- const JSValue u = desc.value;
- if (JS_IsError(ctx, u))
- throw ErrorInfo(getJsString(ctx, u));
- const QString name = getJsString(ctx, prop);
- result[name] = (JS_IsObject(u) && !JS_IsArray(ctx, u) && !JS_IsRegExp(ctx, u))
- ? safeToVariant(ctx, u) : getJsVariant(ctx, u);
- });
- return result;
-}
-
-QVariantMap ModuleLoader::extractParameters(Item *dependsItem) const
-{
- QVariantMap result;
- const Item::PropertyMap &itemProperties = filterItemProperties(
- rootPrototype(dependsItem)->properties());
- if (itemProperties.empty())
- return result;
-
- auto origProperties = dependsItem->properties();
- dependsItem->setProperties(itemProperties);
- JSValue sv = m_evaluator->scriptValue(dependsItem);
- try {
- result = safeToVariant(m_evaluator->engine()->context(), sv);
- } catch (const ErrorInfo &exception) {
- auto ei = exception;
- ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location());
- throw ei;
- }
- dependsItem->setProperties(origProperties);
- return result;
-}
-
-[[noreturn]] static void throwModuleNamePrefixError(const QualifiedId &shortName,
- const QualifiedId &longName, const CodeLocation &codeLocation)
-{
- throw ErrorInfo(Tr::tr("The name of module '%1' is equal to the first component of the "
- "name of module '%2', which is not allowed")
- .arg(shortName.toString(), longName.toString()), codeLocation);
-}
-
-Item *ModuleLoader::moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName)
-{
- QBS_CHECK(!moduleName.empty());
- Item *instance = containerItem;
- for (int i = 0; i < moduleName.size(); ++i) {
- const QString &moduleNameSegment = moduleName.at(i);
- const ValuePtr v = instance->ownProperty(moduleName.at(i));
- if (v && v->type() == Value::ItemValueType) {
- instance = std::static_pointer_cast<ItemValue>(v)->item();
- } else {
- const ItemType itemType = i < moduleName.size() - 1 ? ItemType::ModulePrefix
- : ItemType::ModuleInstance;
- auto newItem = Item::create(m_pool, itemType);
- instance->setProperty(moduleNameSegment, ItemValue::create(newItem));
- instance = newItem;
- }
- if (i < moduleName.size() - 1) {
- if (instance->type() == ItemType::ModuleInstance) {
- QualifiedId conflictingName = QStringList(moduleName.mid(0, i + 1));
- throwModuleNamePrefixError(conflictingName, moduleName, CodeLocation());
- }
- QBS_CHECK(instance->type() == ItemType::ModulePrefix);
- }
- }
- QBS_CHECK(instance != containerItem);
- return instance;
-}
-
-ModuleLoader::ProductModuleInfo *ModuleLoader::productModule(ProductContext *productContext,
- const QString &name, const QString &multiplexId, bool &productNameMatch)
-{
- auto &exportsData = productContext->project->topLevelProject->productModules;
- const auto firstIt = exportsData.find(name);
- productNameMatch = firstIt != exportsData.end();
- for (auto it = firstIt; it != exportsData.end() && it.key() == name; ++it) {
- if (it.value().multiplexId == multiplexId)
- return &it.value();
- }
- if (multiplexId.isEmpty() && firstIt != exportsData.end())
- return &firstIt.value();
- return nullptr;
-}
-
-ModuleLoader::ProductContext *ModuleLoader::product(ProjectContext *projectContext,
- const QString &name)
-{
- auto itEnd = projectContext->products.end();
- auto it = std::find_if(projectContext->products.begin(), itEnd,
- [&name] (const ProductContext &ctx) {
- return ctx.name == name;
- });
- return it == itEnd ? nullptr : &*it;
-}
-
-ModuleLoader::ProductContext *ModuleLoader::product(TopLevelProjectContext *tlpContext,
- const QString &name)
-{
- ProductContext *result = nullptr;
- for (auto prj : tlpContext->projects) {
- result = product(prj, name);
- if (result)
- break;
- }
- return result;
-}
-
-class ModuleLoader::DependsChainManager
-{
-public:
- DependsChainManager(std::vector<DependsChainEntry> &dependsChain, const QualifiedId &module,
- const CodeLocation &dependsLocation)
- : m_dependsChain(dependsChain)
- {
- const bool alreadyInChain = Internal::any_of(dependsChain,
- [&module](const DependsChainEntry &e) {
- return e.name == module;
- });
- if (alreadyInChain) {
- ErrorInfo error;
- error.append(Tr::tr("Cyclic dependencies detected:"));
- for (const DependsChainEntry &e : qAsConst(m_dependsChain))
- error.append(e.name.toString(), e.location);
- error.append(module.toString(), dependsLocation);
- throw error;
- }
- m_dependsChain.emplace_back(module, dependsLocation);
- }
-
- ~DependsChainManager() { m_dependsChain.pop_back(); }
-
-private:
- std::vector<DependsChainEntry> &m_dependsChain;
-};
-
-static bool isBaseModule(QStringView fullModuleName)
-{
- return fullModuleName == StringConstants::qbsModule();
-}
-
-class DelayedPropertyChanger
-{
-public:
- ~DelayedPropertyChanger()
- {
- applyNow();
- }
-
- void setLater(Item *item, const QString &name, const ValuePtr &value)
- {
- QBS_CHECK(m_item == nullptr);
- m_item = item;
- m_name = name;
- m_value = value;
- }
-
- void removeLater(Item *item, const QString &name)
- {
- QBS_CHECK(m_item == nullptr);
- m_item = item;
- m_name = name;
- }
-
- void applyNow()
- {
- if (!m_item || m_name.isEmpty())
- return;
- if (m_value)
- m_item->setProperty(m_name, m_value);
- else
- m_item->removeProperty(m_name);
- m_item = nullptr;
- m_name.clear();
- m_value.reset();
- }
-
-private:
- Item *m_item = nullptr;
- QString m_name;
- ValuePtr m_value;
-};
-
-Item *ModuleLoader::loadModule(ProductContext *productContext, Item *exportingProductItem,
- Item *item, const CodeLocation &dependsItemLocation,
- const QString &moduleId, const QualifiedId &moduleName,
- const QString &multiplexId, FallbackMode fallbackMode,
- bool isRequired, bool *isProductDependency,
- QVariantMap *defaultParameters)
-{
- qCDebug(lcModuleLoader) << "loadModule name:" << moduleName.toString() << "id:" << moduleId;
-
- RequiredChainManager requiredChainManager(m_requiredChain, isRequired);
- DependsChainManager dependsChainManager(m_dependsChain, moduleName, dependsItemLocation);
-
- Item *moduleInstance = moduleId.isEmpty()
- ? moduleInstanceItem(item, moduleName)
- : moduleInstanceItem(item, QStringList(moduleId));
- if (moduleInstance->scope())
- return moduleInstance; // already handled
-
- if (Q_UNLIKELY(moduleInstance->type() == ItemType::ModulePrefix)) {
- for (const Item::Module &m : item->modules()) {
- if (m.name.front() == moduleName.front())
- throwModuleNamePrefixError(moduleName, m.name, dependsItemLocation);
- }
- }
- QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance);
-
- // Prepare module instance for evaluating Module.condition.
- DelayedPropertyChanger delayedPropertyChanger;
- const QString &qbsModuleName = StringConstants::qbsModule();
- const auto fullName = moduleName.toString();
- if (!isBaseModule(fullName)) {
- ItemValuePtr qbsProp = productContext->item->itemProperty(qbsModuleName);
- if (qbsProp) {
- ValuePtr qbsModuleValue = moduleInstance->ownProperty(qbsModuleName);
- if (qbsModuleValue)
- delayedPropertyChanger.setLater(moduleInstance, qbsModuleName, qbsModuleValue);
- else
- delayedPropertyChanger.removeLater(moduleInstance, qbsModuleName);
- moduleInstance->setProperty(qbsModuleName, qbsProp);
- }
- }
-
- SearchPathsManager searchPathsManager(m_reader.get()); // paths can be added by providers
- Item *modulePrototype = nullptr;
- ProductModuleInfo * const pmi = productModule(productContext, fullName,
- multiplexId, *isProductDependency);
- if (pmi) {
- m_dependsChain.back().isProduct = true;
- modulePrototype = pmi->exportItem;
- if (defaultParameters)
- *defaultParameters = pmi->defaultParameters;
- } else if (!*isProductDependency) {
- modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation,
- moduleName, fallbackMode, isRequired, moduleInstance);
- }
- delayedPropertyChanger.applyNow();
- if (!modulePrototype)
- return nullptr;
-
- searchPathsManager.reset(); // deps must be processed in a clean state
-
- instantiateModule(productContext, exportingProductItem, item, moduleInstance, modulePrototype,
- moduleName, pmi);
- return moduleInstance;
-}
-
-struct PrioritizedItem
-{
- PrioritizedItem(Item *item, int priority, int searchPathIndex)
- : item(item), priority(priority), searchPathIndex(searchPathIndex)
- {
- }
-
- Item *item = nullptr;
- int priority = 0;
- int searchPathIndex = 0;
-};
-
-static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidates,
- const QString &moduleName)
-{
- auto maxIt = std::max_element(candidates.begin(), candidates.end(),
- [] (const PrioritizedItem &a, const PrioritizedItem &b) {
- if (a.priority < b.priority)
- return true;
- if (a.priority > b.priority)
- return false;
- return a.searchPathIndex > b.searchPathIndex;
- });
-
- size_t nmax = std::count_if(candidates.begin(), candidates.end(),
- [maxIt] (const PrioritizedItem &i) {
- return i.priority == maxIt->priority && i.searchPathIndex == maxIt->searchPathIndex;
- });
-
- if (nmax > 1) {
- ErrorInfo e(Tr::tr("There is more than one equally prioritized candidate for module '%1'.")
- .arg(moduleName));
- for (size_t i = 0; i < candidates.size(); ++i) {
- const auto candidate = candidates.at(i);
- if (candidate.priority == maxIt->priority) {
- //: The %1 denotes the number of the candidate.
- e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i).item->location());
- }
- }
- throw e;
- }
-
- return maxIt->item;
-}
-
-Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext,
- const CodeLocation &dependsItemLocation, const QualifiedId &moduleName,
- FallbackMode fallbackMode, bool isRequired, Item *moduleInstance)
-{
- auto existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName);
-
- if (existingPaths.isEmpty()) { // no suitable names found, try to use providers
- AccumulatingTimer providersTimer(
- m_parameters.logElapsedTime() ? &m_elapsedTimeModuleProviders : nullptr);
- auto result = m_moduleProviderLoader->executeModuleProviders(
- *productContext,
- dependsItemLocation,
- moduleName,
- fallbackMode);
- if (result.providerAddedSearchPaths) {
- qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString()
- << "with newly added search paths from module provider";
- existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName);
- }
- }
-
- const QString fullName = 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] = loadModuleFile(
- productContext, fullName, filePath, moduleInstance);
- if (module)
- candidates.emplace_back(module, 0, i);
- if (!triedToLoad)
- it = moduleFileNames.erase(it);
- else
- ++it;
- triedToLoadModule = triedToLoadModule || triedToLoad;
- }
- }
-
- if (candidates.empty()) {
- if (!isRequired)
- return createNonPresentModule(fullName, QStringLiteral("not found"), nullptr);
- if (Q_UNLIKELY(triedToLoadModule)) {
- throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName),
- dependsItemLocation);
- }
- return nullptr;
- }
-
- Item *moduleItem;
- if (candidates.size() == 1) {
- moduleItem = candidates.at(0).item;
- } else {
- for (auto &candidate : candidates) {
- candidate.priority = m_evaluator->intValue(candidate.item,
- StringConstants::priorityProperty(),
- candidate.priority);
- }
- moduleItem = chooseModuleCandidate(candidates, fullName);
- }
-
- const auto it = productContext->unknownProfilePropertyErrors.find(moduleItem);
- if (it != productContext->unknownProfilePropertyErrors.cend()) {
- const QString fullProductName = ResolvedProduct::fullDisplayName
- (productContext->name, productContext->multiplexConfigurationId);
- ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values "
- "in profile '%3':").arg(fullName, fullProductName,
- productContext->profileName));
- for (const ErrorInfo &e : it->second)
- error.append(e.toString());
- handlePropertyError(error, m_parameters, m_logger);
- }
- return moduleItem;
-}
-
-QStringList &ModuleLoader::getModuleFileNames(const QString &dirPath)
-{
- QStringList &moduleFileNames = m_moduleDirListCache[dirPath];
- if (moduleFileNames.empty()) {
- QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards());
- while (dirIter.hasNext())
- moduleFileNames += dirIter.next();
- }
- return moduleFileNames;
-}
-
-static Item *findDeepestModuleInstance(Item *instance)
-{
- while (instance->prototype() && instance->prototype()->type() == ItemType::ModuleInstance)
- instance = instance->prototype();
- return instance;
-}
-
-std::pair<Item *, bool> ModuleLoader::loadModuleFile(
- ProductContext *productContext, const QString &fullModuleName,
- const QString &filePath, Item *moduleInstance)
-{
- checkCancelation();
-
- qCDebug(lcModuleLoader) << "loadModuleFile" << fullModuleName << "from" << filePath;
-
- const auto [module, triedToLoad] =
- getModulePrototype(productContext, fullModuleName, filePath);
- if (!module)
- return {nullptr, triedToLoad};
-
- const auto key = std::make_pair(module, productContext);
- const auto it = m_modulePrototypeEnabledInfo.find(key);
- if (it != m_modulePrototypeEnabledInfo.end()) {
- qCDebug(lcModuleLoader) << "prototype cache hit (level 2)";
- return {it.value() ? module : nullptr, triedToLoad};
- }
-
- // Set the name before evaluating any properties. Evaluator reads the module name.
- module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName));
-
- Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance);
- Item *origDeepestModuleInstancePrototype = deepestModuleInstance->prototype();
- deepestModuleInstance->setPrototype(module);
- bool enabled = checkItemCondition(moduleInstance, module);
- deepestModuleInstance->setPrototype(origDeepestModuleInstancePrototype);
- if (!enabled) {
- qCDebug(lcModuleLoader) << "condition of module" << fullModuleName << "is false";
- m_modulePrototypeEnabledInfo.insert(key, false);
- return {nullptr, triedToLoad};
- }
-
- if (isBaseModule(fullModuleName))
- setupBaseModulePrototype(module);
- else
- resolveParameterDeclarations(module);
-
- m_modulePrototypeEnabledInfo.insert(key, true);
- return {module, triedToLoad};
-}
-
-// Returns the module prototype item and a boolean indicating if we tried to load it from the file
-std::pair<Item *, bool> ModuleLoader::getModulePrototype(ProductContext *productContext,
- const QString &fullModuleName, const QString &filePath)
-{
- auto &prototypeList = m_modulePrototypes[filePath];
- for (const auto &prototype : prototypeList) {
- if (prototype.second == productContext->profileName) {
- qCDebug(lcModuleLoader) << "prototype cache hit (level 1)";
- return {prototype.first, true};
- }
- }
- Item * const module = loadItemFromFile(filePath, CodeLocation());
- if (module->type() != ItemType::Module) {
- qCDebug(lcModuleLoader).nospace()
- << "Alleged module " << fullModuleName << " has type '"
- << module->typeName() << "', so it's not a module after all.";
- return {nullptr, false};
- }
- prototypeList.emplace_back(module, productContext->profileName);
-
- // Module properties that are defined in the profile are used as default values.
- // This is the reason we need to have different items per profile.
- const QVariantMap profileModuleProperties
- = productContext->moduleProperties.value(fullModuleName).toMap();
- for (auto it = profileModuleProperties.cbegin(); it != profileModuleProperties.cend(); ++it) {
- if (Q_UNLIKELY(!module->hasProperty(it.key()))) {
- productContext->unknownProfilePropertyErrors[module].emplace_back
- (Tr::tr("Unknown property: %1.%2").arg(fullModuleName, it.key()));
- continue;
- }
- const PropertyDeclaration decl = module->propertyDeclaration(it.key());
- VariantValuePtr v = VariantValue::create(
- PropertyDeclaration::convertToPropertyType(it.value(), decl.type(),
- QStringList(fullModuleName), it.key()));
- module->setProperty(it.key(), v);
- }
-
- return {module, true};
-}
-
-Item::Module ModuleLoader::loadBaseModule(ProductContext *productContext, Item *item)
-{
- const QualifiedId baseModuleName(StringConstants::qbsModule());
- Item::Module baseModuleDesc;
- baseModuleDesc.name = baseModuleName;
- baseModuleDesc.item = loadModule(productContext, nullptr, item, CodeLocation(), QString(),
- baseModuleName, QString(), FallbackMode::Disabled, true,
- &baseModuleDesc.isProduct, nullptr);
- if (productContext->item) {
- const Item * const qbsInstanceItem
- = moduleInstanceItem(productContext->item, baseModuleName);
- const Item::PropertyMap &props = qbsInstanceItem->properties();
- for (auto it = props.cbegin(); it != props.cend(); ++it) {
- if (it.value()->type() == Value::VariantValueType)
- baseModuleDesc.item->setProperty(it.key(), it.value());
- }
- }
- QBS_CHECK(!baseModuleDesc.isProduct);
- if (Q_UNLIKELY(!baseModuleDesc.item))
- throw ErrorInfo(Tr::tr("Cannot load base qbs module."));
- return baseModuleDesc;
-}
-
-void ModuleLoader::setupBaseModulePrototype(Item *prototype)
-{
- prototype->setProperty(QStringLiteral("hostPlatform"),
- VariantValue::create(HostOsInfo::hostOSIdentifier()));
- prototype->setProperty(QStringLiteral("hostArchitecture"),
- VariantValue::create(HostOsInfo::hostOSArchitecture()));
- prototype->setProperty(QStringLiteral("libexecPath"),
- VariantValue::create(m_parameters.libexecPath()));
-
- const Version qbsVersion = LanguageInfo::qbsVersion();
- prototype->setProperty(QStringLiteral("versionMajor"),
- VariantValue::create(qbsVersion.majorVersion()));
- prototype->setProperty(QStringLiteral("versionMinor"),
- VariantValue::create(qbsVersion.minorVersion()));
- prototype->setProperty(QStringLiteral("versionPatch"),
- VariantValue::create(qbsVersion.patchLevel()));
-}
-
-static void collectItemsWithId_impl(Item *item, QList<Item *> *result)
-{
- if (!item->id().isEmpty())
- result->push_back(item);
- for (Item * const child : item->children())
- collectItemsWithId_impl(child, result);
-}
-
-static QList<Item *> collectItemsWithId(Item *item)
-{
- QList<Item *> result;
- collectItemsWithId_impl(item, &result);
- return result;
-}
-
-static std::vector<std::pair<QualifiedId, ItemValuePtr>> instanceItemProperties(Item *item)
-{
- std::vector<std::pair<QualifiedId, ItemValuePtr>> result;
- QualifiedId name;
- const auto func = [&] (Item *item, const auto &f) -> void {
- for (auto it = item->properties().begin(), end = item->properties().end();
- it != end; ++it) {
- if (it.value()->type() != Value::ItemValueType)
- continue;
- ItemValuePtr itemValue = std::static_pointer_cast<ItemValue>(it.value());
- if (!itemValue->item())
- continue;
- name.push_back(it.key());
- if (itemValue->item()->type() == ItemType::ModulePrefix)
- f(itemValue->item(), f);
- else
- result.emplace_back(name, itemValue);
- name.removeLast();
- }
- };
- func(item, func);
- return result;
-}
-
-void ModuleLoader::instantiateModule(ProductContext *productContext, Item *exportingProduct,
- Item *instanceScope, Item *moduleInstance, Item *modulePrototype,
- const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo)
-{
- Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance);
- deepestModuleInstance->setPrototype(modulePrototype);
- const QString fullName = moduleName.toString();
- const QString generalOverrideKey = QStringLiteral("modules.") + fullName;
- const QString perProductOverrideKey = StringConstants::productsOverridePrefix()
- + productContext->name + QLatin1Char('.') + fullName;
- for (Item *instance = moduleInstance; instance; instance = instance->prototype()) {
- overrideItemProperties(instance, generalOverrideKey, m_parameters.overriddenValuesTree());
- if (fullName == QStringLiteral("qbs"))
- overrideItemProperties(instance, fullName, m_parameters.overriddenValuesTree());
- overrideItemProperties(instance, perProductOverrideKey,
- m_parameters.overriddenValuesTree());
- if (instance == deepestModuleInstance)
- break;
- }
-
- moduleInstance->setFile(modulePrototype->file());
- moduleInstance->setLocation(modulePrototype->location());
- QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance);
-
- // create module scope
- Item *moduleScope = Item::create(m_pool, ItemType::Scope);
- QBS_CHECK(instanceScope->file());
- moduleScope->setFile(instanceScope->file());
- moduleScope->setScope(instanceScope);
- QBS_CHECK(productContext->project->scope);
- productContext->project->scope->copyProperty(StringConstants::projectVar(), moduleScope);
- if (productContext->scope)
- productContext->scope->copyProperty(StringConstants::productVar(), moduleScope);
- else
- QBS_CHECK(fullName == StringConstants::qbsModule()); // Dummy product.
-
- if (productModuleInfo) {
- exportingProduct = productModuleInfo->exportItem->parent();
- QBS_CHECK(exportingProduct);
- QBS_CHECK(exportingProduct->type() == ItemType::Product);
- }
-
- if (exportingProduct) {
- const auto exportingProductItemValue = ItemValue::create(exportingProduct);
- moduleScope->setProperty(QStringLiteral("exportingProduct"), exportingProductItemValue);
-
- const auto importingProductItemValue = ItemValue::create(productContext->item);
- moduleScope->setProperty(QStringLiteral("importingProduct"), importingProductItemValue);
-
- moduleScope->setProperty(StringConstants::projectVar(),
- ItemValue::create(exportingProduct->parent()));
-
- PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(),
- PropertyDeclaration::String, QString(),
- PropertyDeclaration::PropertyNotAvailableInConfig);
- moduleInstance->setPropertyDeclaration(pd.name(), pd);
- ValuePtr v = exportingProduct
- ->property(StringConstants::sourceDirectoryProperty())->clone();
- moduleInstance->setProperty(pd.name(), v);
- }
- moduleInstance->setScope(moduleScope);
-
- QHash<Item *, Item *> prototypeInstanceMap;
- prototypeInstanceMap[modulePrototype] = moduleInstance;
-
- // create instances for every child of the prototype
- createChildInstances(moduleInstance, modulePrototype, &prototypeInstanceMap);
-
- // create ids from from the prototype in the instance
- if (modulePrototype->file()->idScope()) {
- const auto items = collectItemsWithId(modulePrototype);
- for (Item * const itemWithId : items) {
- Item *idProto = itemWithId;
- Item *idInstance = prototypeInstanceMap.value(idProto);
- QBS_ASSERT(idInstance, continue);
- ItemValuePtr idInstanceValue = ItemValue::create(idInstance);
- moduleScope->setProperty(itemWithId->id(), idInstanceValue);
- }
- }
-
- // For foo.bar in modulePrototype create an item foo in moduleInstance.
- for (const auto &[propertyName, itemValue] : instanceItemProperties(modulePrototype)) {
- if (itemValue->item()->properties().empty())
- continue;
- qCDebug(lcModuleLoader) << "The prototype of " << moduleName
- << " sets properties on " << propertyName.toString();
- Item *item = moduleInstanceItem(moduleInstance, propertyName);
- item->setPrototype(itemValue->item());
- if (itemValue->createdByPropertiesBlock()) {
- ItemValuePtr itemValue = moduleInstance->itemProperty(propertyName.front());
- for (int i = 1; i < propertyName.size(); ++i)
- itemValue = itemValue->item()->itemProperty(propertyName.at(i));
- itemValue->setCreatedByPropertiesBlock(true);
- }
- }
-
- // Resolve dependencies of this module instance.
- DependsContext dependsContext;
- dependsContext.product = productContext;
- dependsContext.exportingProductItem = exportingProduct;
- QBS_ASSERT(moduleInstance->modules().empty(), moduleInstance->removeModules());
- if (productModuleInfo) {
- dependsContext.productDependencies = &productContext->productModuleDependencies[fullName];
- resolveDependencies(&dependsContext, moduleInstance);
- } else if (!isBaseModule(fullName)) {
- dependsContext.productDependencies = &productContext->info.usedProducts;
- resolveDependencies(&dependsContext, moduleInstance);
- }
-
- // Check readonly properties.
- const auto end = moduleInstance->properties().cend();
- for (auto it = moduleInstance->properties().cbegin(); it != end; ++it) {
- const PropertyDeclaration &pd = moduleInstance->propertyDeclaration(it.key());
- if (!pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag))
- continue;
- throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()),
- moduleInstance->property(pd.name())->location());
- }
-}
-
-void ModuleLoader::createChildInstances(Item *instance, Item *prototype,
- QHash<Item *, Item *> *prototypeInstanceMap) const
-{
- instance->childrenReserve(instance->children().size() + prototype->children().size());
-
- for (Item * const childPrototype : prototype->children()) {
- Item *childInstance = Item::create(m_pool, childPrototype->type());
- prototypeInstanceMap->insert(childPrototype, childInstance);
- childInstance->setPrototype(childPrototype);
- childInstance->setFile(childPrototype->file());
- childInstance->setId(childPrototype->id());
- childInstance->setLocation(childPrototype->location());
- childInstance->setScope(instance->scope());
- Item::addChild(instance, childInstance);
- createChildInstances(childInstance, childPrototype, prototypeInstanceMap);
- }
-}
-
-void ModuleLoader::checkCancelation() const
-{
- if (m_progressObserver && m_progressObserver->canceled()) {
- throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.")
- .arg(TopLevelProject::deriveId(m_parameters.finalBuildConfigurationTree())));
- }
-}
-
-bool ModuleLoader::checkItemCondition(Item *item, Item *itemToDisable)
-{
- if (m_evaluator->boolValue(item, StringConstants::conditionProperty()))
- return true;
- m_disabledItems += itemToDisable ? itemToDisable : item;
- return false;
-}
-
-QStringList ModuleLoader::readExtraSearchPaths(Item *item, bool *wasSet)
-{
- QStringList result;
- const QStringList paths = m_evaluator->stringListValue(
- item, StringConstants::qbsSearchPathsProperty(), wasSet);
- const JSSourceValueConstPtr prop = item->sourceProperty(
- StringConstants::qbsSearchPathsProperty());
-
- // Value can come from within a project file or as an overridden value from the user
- // (e.g command line).
- const QString basePath = FileInfo::path(prop ? prop->file()->filePath()
- : m_parameters.projectFilePath());
- for (const QString &path : paths)
- result += FileInfo::resolvePath(basePath, path);
- return result;
-}
-
-void ModuleLoader::copyProperties(const Item *sourceProject, Item *targetProject)
-{
- if (!sourceProject)
- return;
- const QList<PropertyDeclaration> builtinProjectProperties = BuiltinDeclarations::instance()
- .declarationsForType(ItemType::Project).properties();
- Set<QString> builtinProjectPropertyNames;
- for (const PropertyDeclaration &p : builtinProjectProperties)
- builtinProjectPropertyNames << p.name();
-
- for (Item::PropertyDeclarationMap::ConstIterator it
- = sourceProject->propertyDeclarations().constBegin();
- it != sourceProject->propertyDeclarations().constEnd(); ++it) {
-
- // We must not inherit built-in properties such as "name",
- // but there are exceptions.
- if (it.key() == StringConstants::qbsSearchPathsProperty()
- || it.key() == StringConstants::profileProperty()
- || it.key() == StringConstants::buildDirectoryProperty()
- || it.key() == StringConstants::sourceDirectoryProperty()
- || it.key() == StringConstants::minimumQbsVersionProperty()) {
- const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key());
- QBS_ASSERT(v, continue);
- if (v->sourceCode() == StringConstants::undefinedValue())
- sourceProject->copyProperty(it.key(), targetProject);
- continue;
- }
-
- if (builtinProjectPropertyNames.contains(it.key()))
- continue;
-
- if (targetProject->hasOwnProperty(it.key()))
- continue; // Ignore stuff the target project already has.
-
- targetProject->setPropertyDeclaration(it.key(), it.value());
- sourceProject->copyProperty(it.key(), targetProject);
- }
-}
-
-Item *ModuleLoader::wrapInProjectIfNecessary(Item *item)
-{
- if (item->type() == ItemType::Project)
- return item;
- Item *prj = Item::create(item->pool(), ItemType::Project);
- Item::addChild(prj, item);
- prj->setFile(item->file());
- prj->setLocation(item->location());
- prj->setupForBuiltinType(m_parameters.deprecationWarningMode(), m_logger);
- return prj;
-}
-
-QString ModuleLoader::findExistingModulePath(const QString &searchPath,
- const QualifiedId &moduleName)
-{
- // 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 = m_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);
-}
-
-QStringList ModuleLoader::findExistingModulePaths(
- const QStringList &searchPaths, const QualifiedId &moduleName)
-{
- QStringList result;
- result.reserve(searchPaths.size());
- for (const auto &path: searchPaths) {
- const QString dirPath = findExistingModulePath(path, moduleName);
- if (!dirPath.isEmpty())
- result.append(dirPath);
- }
- return result;
-}
-
-void ModuleLoader::setScopeForDescendants(Item *item, Item *scope)
-{
- for (Item * const child : item->children()) {
- child->setScope(scope);
- setScopeForDescendants(child, scope);
- }
-}
-
-void ModuleLoader::overrideItemProperties(Item *item, const QString &buildConfigKey,
- const QVariantMap &buildConfig)
-{
- const QVariant buildConfigValue = buildConfig.value(buildConfigKey);
- if (buildConfigValue.isNull())
- return;
- item->overrideProperties(buildConfigValue.toMap(), buildConfigKey, m_parameters, m_logger);
-}
-
-void ModuleLoader::collectAllModules(Item *item, std::vector<Item::Module> *modules)
-{
- for (const Item::Module &m : item->modules()) {
- if (moduleRepresentsDisabledProduct(m))
- m.item->removeModules();
- auto it = std::find_if(modules->begin(), modules->end(),
- [m] (const Item::Module &m2) { return m.name == m2.name; });
- if (it != modules->end()) {
- // If a module is required somewhere, it is required in the top-level item.
- if (m.required)
- it->required = true;
- it->versionRange.narrowDown(m.versionRange);
- it->fallbackEnabled = it->fallbackEnabled && m.fallbackEnabled;
- continue;
- }
- modules->push_back(m);
- collectAllModules(m.item, modules);
- }
-}
-
-std::vector<Item::Module> ModuleLoader::allModules(Item *item)
-{
- std::vector<Item::Module> lst;
- collectAllModules(item, &lst);
- return lst;
-}
-
-bool ModuleLoader::moduleRepresentsDisabledProduct(const Item::Module &module)
-{
- if (!module.isProduct)
- return false;
- const Item *exportItem = module.item->prototype();
- while (exportItem && exportItem->type() != ItemType::Export)
- exportItem = exportItem->prototype();
- QBS_CHECK(exportItem);
- Item * const productItem = exportItem->parent();
- QBS_CHECK(productItem->type() == ItemType::Product);
- return m_disabledItems.contains(productItem) || !checkItemCondition(productItem);
-}
-
-void ModuleLoader::addProductModuleDependencies(ProductContext *productContext, const QString &name)
-{
- auto deps = productContext->productModuleDependencies.at(name);
- QList<ModuleLoaderResult::ProductInfo::Dependency> depsToAdd;
- const bool productIsMultiplexed = !productContext->multiplexConfigurationId.isEmpty();
- for (auto &dep : deps) {
- const auto productRange = m_productsByName.equal_range(dep.name);
- std::vector<const ProductContext *> dependencies;
- bool hasNonMultiplexedDependency = false;
- for (auto it = productRange.first; it != productRange.second; ++it) {
- if (!it->second->multiplexConfigurationId.isEmpty()) {
- dependencies.push_back(it->second);
- if (productIsMultiplexed && dep.profile.isEmpty())
- break;
- } else {
- hasNonMultiplexedDependency = true;
- break;
- }
- }
-
- if (hasNonMultiplexedDependency) {
- depsToAdd.push_back(dep);
- continue;
- }
-
- for (std::size_t i = 0; i < dependencies.size(); ++i) {
- const bool profileMatch = dep.profile.isEmpty()
- || dep.profile == StringConstants::star()
- || dep.profile == dependencies.at(i)->profileName;
- if (i == 0) {
- if (productIsMultiplexed && dep.profile.isEmpty()) {
- const ValuePtr &multiplexConfigIdProp = productContext->item->property(
- StringConstants::multiplexConfigurationIdProperty());
- dep.multiplexConfigurationId = std::static_pointer_cast<VariantValue>(
- multiplexConfigIdProp)->value().toString();
- depsToAdd.push_back(dep);
- break;
- }
- if (profileMatch) {
- dep.multiplexConfigurationId = dependencies.at(i)->multiplexConfigurationId;
- depsToAdd.push_back(dep);
- }
- } else if (profileMatch) {
- ModuleLoaderResult::ProductInfo::Dependency newDependency = dep;
- newDependency.multiplexConfigurationId
- = dependencies.at(i)->multiplexConfigurationId;
- depsToAdd << newDependency;
- }
- }
- }
- productContext->info.usedProducts.insert(productContext->info.usedProducts.end(),
- depsToAdd.cbegin(), depsToAdd.cend());
-}
-
-static void collectProductModuleDependencies(Item *item, Set<QualifiedId> &allDeps)
-{
- for (const Item::Module &m : item->modules()) {
- if (m.isProduct && allDeps.insert(m.name).second)
- collectProductModuleDependencies(m.item, allDeps);
- }
-}
-
-void ModuleLoader::addProductModuleDependencies(ModuleLoader::ProductContext *ctx)
-{
- Set<QualifiedId> deps;
- collectProductModuleDependencies(ctx->item, deps);
- for (const QualifiedId &dep : deps)
- addProductModuleDependencies(ctx, dep.toString());
-}
-
-void ModuleLoader::addTransitiveDependencies(ProductContext *ctx)
-{
- AccumulatingTimer timer(m_parameters.logElapsedTime()
- ? &m_elapsedTimeTransitiveDependencies : nullptr);
- qCDebug(lcModuleLoader) << "addTransitiveDependencies";
-
- std::vector<Item::Module> transitiveDeps = allModules(ctx->item);
- std::sort(transitiveDeps.begin(), transitiveDeps.end());
- for (const Item::Module &m : ctx->item->modules()) {
- auto it = std::lower_bound(transitiveDeps.begin(), transitiveDeps.end(), m);
- QBS_CHECK(it != transitiveDeps.end() && it->name == m.name);
- transitiveDeps.erase(it);
- }
- for (const Item::Module &module : qAsConst(transitiveDeps)) {
- if (module.isProduct) {
- ctx->item->addModule(module);
- } else {
- const FallbackMode fallbackMode = module.fallbackEnabled
- ? FallbackMode::Enabled : FallbackMode::Disabled;
- Item::Module dep;
- dep.item = loadModule(ctx, nullptr, ctx->item, ctx->item->location(), QString(),
- module.name, QString(), fallbackMode,
- module.required, &dep.isProduct, &dep.parameters);
- if (!dep.item) {
- throw ErrorInfo(Tr::tr("Module '%1' not found when setting up transitive "
- "dependencies for product '%2'.").arg(module.name.toString(),
- ctx->name),
- ctx->item->location());
- }
- dep.name = module.name;
- dep.required = module.required;
- dep.versionRange = module.versionRange;
- dep.fallbackEnabled = fallbackMode == FallbackMode::Enabled;
- ctx->item->addModule(dep);
- }
- }
-}
-
-Item *ModuleLoader::createNonPresentModule(const QString &name, const QString &reason, Item *module)
-{
- qCDebug(lcModuleLoader) << "Non-required module '" << name << "' not loaded (" << reason << ")."
- << "Creating dummy module for presence check.";
- if (!module) {
- module = Item::create(m_pool, ItemType::ModuleInstance);
- module->setFile(FileContext::create());
- module->setProperty(StringConstants::nameProperty(), VariantValue::create(name));
- }
- module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue());
- return module;
-}
-
-void ModuleLoader::handleProductError(const ErrorInfo &error,
- ModuleLoader::ProductContext *productContext)
-{
- const bool alreadyHadError = productContext->info.delayedError.hasError();
- if (!alreadyHadError) {
- productContext->info.delayedError.append(Tr::tr("Error while handling product '%1':")
- .arg(productContext->name),
- productContext->item->location());
- }
- if (error.isInternalError()) {
- if (alreadyHadError) {
- qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString()
- << "in product" << productContext->name;
- return;
- }
- for (const auto &kv : productContext->productModuleDependencies) {
- const auto rangeForName = m_productsByName.equal_range(kv.first);
- for (auto rangeIt = rangeForName.first; rangeIt != rangeForName.second; ++rangeIt) {
- const ProductContext * const dep = rangeIt->second;
- if (dep->info.delayedError.hasError()) {
- qCDebug(lcModuleLoader()) << "ignoring internal error" << error.toString()
- << "in product" << productContext->name
- << "assumed to be caused by erroneous dependency"
- << dep->name;
- return;
- }
- }
- }
- }
- const auto errorItems = error.items();
- for (const ErrorItem &ei : errorItems)
- productContext->info.delayedError.append(ei.description(), ei.codeLocation());
- productContext->project->result->productInfos[productContext->item] = productContext->info;
- m_disabledItems << productContext->item;
- m_erroneousProducts.insert(productContext->name);
-}
-
-static void gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
- QualifiedIdSet &properties)
-{
- const Item::PropertyMap &props = iv->item()->properties();
- for (auto it = props.cbegin(); it != props.cend(); ++it) {
- switch (it.value()->type()) {
- case Value::JSSourceValueType:
- properties << (QualifiedId(prefix) << it.key());
- break;
- case Value::ItemValueType:
- if (iv->item()->type() == ItemType::ModulePrefix) {
- gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(),
- QualifiedId(prefix) << it.key(), properties);
- }
- break;
- default:
- break;
- }
- }
-}
-
-QualifiedIdSet ModuleLoader::gatherModulePropertiesSetInGroup(const Item *group)
-{
- QualifiedIdSet propsSetInGroup;
- const Item::PropertyMap &props = group->properties();
- for (auto it = props.cbegin(); it != props.cend(); ++it) {
- if (it.value()->type() == Value::ItemValueType) {
- gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(),
- QualifiedId(it.key()), propsSetInGroup);
- }
- }
- return propsSetInGroup;
-}
-
-void ModuleLoader::markModuleTargetGroups(Item *group, const Item::Module &module)
-{
- QBS_CHECK(group->type() == ItemType::Group);
- if (m_evaluator->boolValue(group, StringConstants::filesAreTargetsProperty())) {
- group->setProperty(StringConstants::modulePropertyInternal(),
- VariantValue::create(module.name.toString()));
- }
- for (Item * const child : group->children())
- markModuleTargetGroups(child, module);
-}
-
-void ModuleLoader::copyGroupsFromModuleToProduct(const ProductContext &productContext,
- const Item::Module &module,
- const Item *modulePrototype)
-{
- for (Item * const child : modulePrototype->children()) {
- if (child->type() == ItemType::Group) {
- Item * const clonedGroup = child->clone();
- clonedGroup->setScope(productContext.scope);
- setScopeForDescendants(clonedGroup, productContext.scope);
- Item::addChild(productContext.item, clonedGroup);
- markModuleTargetGroups(clonedGroup, module);
- }
- }
-}
-
-void ModuleLoader::copyGroupsFromModulesToProduct(const ProductContext &productContext)
-{
- for (const Item::Module &module : productContext.item->modules()) {
- Item *prototype = module.item;
- bool modulePassedValidation;
- while ((modulePassedValidation = prototype->isPresentModule()) && prototype->prototype())
- prototype = prototype->prototype();
- if (modulePassedValidation)
- copyGroupsFromModuleToProduct(productContext, module, prototype);
- }
-}
-
-QString ModuleLoaderResult::ProductInfo::Dependency::uniqueName() const
-{
- return ResolvedProduct::uniqueName(name, multiplexConfigurationId);
-}
-
-QString ModuleLoader::ProductContext::uniqueName() const
-{
- return ResolvedProduct::uniqueName(name, multiplexConfigurationId);
-}
-
-} // namespace Internal
-} // namespace qbs
diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h
deleted file mode 100644
index db0b51cae..000000000
--- a/src/lib/corelib/language/moduleloader.h
+++ /dev/null
@@ -1,459 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef QBS_MODULELOADER_H
-#define QBS_MODULELOADER_H
-
-#include "filetags.h"
-#include "forward_decls.h"
-#include "item.h"
-#include "itempool.h"
-#include "moduleproviderinfo.h"
-#include <logging/logger.h>
-#include <tools/filetime.h>
-#include <tools/qttools.h>
-#include <tools/set.h>
-#include <tools/setupprojectparameters.h>
-#include <tools/version.h>
-
-#include <QtCore/qmap.h>
-#include <QtCore/qstringlist.h>
-#include <QtCore/qvariant.h>
-
-#include <map>
-#include <memory>
-#include <optional>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-namespace qbs {
-
-class CodeLocation;
-class Settings;
-
-namespace Internal {
-
-class Evaluator;
-class Item;
-class ItemReader;
-class ModuleProviderLoader;
-class ProbesResolver;
-class ProgressObserver;
-class QualifiedId;
-class SearchPathsManager;
-
-using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>;
-
-struct ModuleLoaderResult
-{
- ModuleLoaderResult()
- : itemPool(new ItemPool), root(nullptr)
- {}
-
- struct ProductInfo
- {
- struct Dependency
- {
- QString name;
- QString profile; // "*" <=> Match all profiles.
- QString multiplexConfigurationId;
- QVariantMap parameters;
- bool limitToSubProject = false;
- bool isRequired = true;
-
- QString uniqueName() const;
- };
-
- std::vector<ProbeConstPtr> probes;
- std::vector<Dependency> usedProducts;
- ModulePropertiesPerGroup modulePropertiesSetInGroups;
- ErrorInfo delayedError;
- };
-
- std::shared_ptr<ItemPool> itemPool;
- Item *root;
- std::unordered_map<Item *, ProductInfo> productInfos;
- std::vector<ProbeConstPtr> projectProbes;
- StoredModuleProviderInfo storedModuleProviderInfo;
- Set<QString> qbsFiles;
- QVariantMap profileConfigs;
-};
-
-/*
- * Loader stage II. Responsible for
- * - loading modules and module dependencies,
- * - project references,
- * - Probe items.
- */
-class ModuleLoader
-{
-public:
- ModuleLoader(Evaluator *evaluator, Logger &logger);
- ~ModuleLoader();
-
- void setProgressObserver(ProgressObserver *progressObserver);
- void setSearchPaths(const QStringList &searchPaths);
- void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
- void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; }
- void setStoredProfiles(const QVariantMap &profiles);
- void setStoredModuleProviderInfo(const StoredModuleProviderInfo &moduleProviderInfo);
- Evaluator *evaluator() const { return m_evaluator; }
-
- ModuleLoaderResult load(const SetupProjectParameters &parameters);
-
-private:
- friend class ModuleProviderLoader;
- friend class ProbesResolver;
- class ProductSortByDependencies;
-
- class ContextBase
- {
- public:
- ContextBase()
- : item(nullptr), scope(nullptr)
- {}
-
- Item *item;
- Item *scope;
- QString name;
- };
-
- class ProjectContext;
-
- using ProductDependencies = std::vector<ModuleLoaderResult::ProductInfo::Dependency>;
-
- // This is the data we need to store at the point where a dependency is deferred
- // in order to properly resolve the dependency in pass 2.
- struct DeferredDependsContext {
- DeferredDependsContext(Item *exportingProduct, Item *parent)
- : exportingProductItem(exportingProduct), parentItem(parent) {}
- Item *exportingProductItem = nullptr;
- Item *parentItem = nullptr;
- bool operator==(const DeferredDependsContext &other) const
- {
- return exportingProductItem == other.exportingProductItem
- && parentItem == other.parentItem;
- }
- bool operator<(const DeferredDependsContext &other) const
- {
- return parentItem < other.parentItem;
- }
- };
-
- class ProductContext : public ContextBase
- {
- public:
- ProjectContext *project = nullptr;
- ModuleLoaderResult::ProductInfo info;
- QString profileName;
- QString multiplexConfigurationId;
- QVariantMap moduleProperties;
- std::map<QString, ProductDependencies> productModuleDependencies;
- std::unordered_map<const Item *, std::vector<ErrorInfo>> unknownProfilePropertyErrors;
- QStringList searchPaths;
-
- std::optional<QVariantMap> theModuleProviderConfig;
-
- // The key corresponds to DeferredDependsContext.exportingProductItem, which is the
- // only value from that data structure that we still need here.
- std::unordered_map<Item *, std::vector<Item *>> deferredDependsItems;
-
- QString uniqueName() const;
- };
-
- class TopLevelProjectContext;
-
- class ProjectContext : public ContextBase
- {
- public:
- TopLevelProjectContext *topLevelProject = nullptr;
- ModuleLoaderResult *result = nullptr;
- std::vector<ProductContext> products;
- std::vector<QStringList> searchPathsStack;
- };
-
- struct ProductModuleInfo
- {
- Item *exportItem = nullptr;
- QString multiplexId;
- QVariantMap defaultParameters;
- };
-
- class TopLevelProjectContext
- {
- Q_DISABLE_COPY(TopLevelProjectContext)
- public:
- TopLevelProjectContext() = default;
- ~TopLevelProjectContext() { qDeleteAll(projects); }
-
- std::vector<ProjectContext *> projects;
- QMultiHash<QString, ProductModuleInfo> productModules;
- std::vector<ProbeConstPtr> probes;
- QString buildDirectory;
- };
-
- class DependsContext
- {
- public:
- ProductContext *product = nullptr;
- Item *exportingProductItem = nullptr;
- ProductDependencies *productDependencies = nullptr;
- };
-
- void handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem,
- const QString &buildDirectory, const Set<QString> &referencedFilePaths);
- void handleProject(ModuleLoaderResult *loadResult,
- TopLevelProjectContext *topLevelProjectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths);
-
- using MultiplexRow = std::vector<VariantValuePtr>;
- using MultiplexTable = std::vector<MultiplexRow>;
-
- struct MultiplexInfo
- {
- std::vector<QString> properties;
- MultiplexTable table;
- bool aggregate = false;
- VariantValuePtr multiplexedType;
-
- QString toIdString(size_t row) const;
- static QVariantMap multiplexIdToVariantMap(const QString &multiplexId);
- };
-
- void dump(const MultiplexInfo &mpi);
- static MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values);
- MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem);
- QList<Item *> multiplexProductItem(ProductContext *dummyContext, Item *productItem);
- void normalizeDependencies(ProductContext *product,
- const DeferredDependsContext &dependsContext);
- void adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp);
- void adjustDependenciesForMultiplexing(const ProductContext &product);
- void adjustDependenciesForMultiplexing(const ProductContext &product, Item *dependsItem);
-
- void prepareProduct(ProjectContext *projectContext, Item *productItem);
- void setupProductDependencies(ProductContext *productContext,
- const Set<DeferredDependsContext> &deferredDependsContext);
- void handleProduct(ProductContext *productContext);
- void checkDependencyParameterDeclarations(const ProductContext *productContext) const;
- void handleModuleSetupError(ProductContext *productContext, const Item::Module &module,
- const ErrorInfo &error);
- void initProductProperties(const ProductContext &product);
- void handleSubProject(ProjectContext *projectContext, Item *projectItem,
- const Set<QString> &referencedFilePaths);
- QList<Item *> loadReferencedFile(const QString &relativePath,
- const CodeLocation &referencingLocation,
- const Set<QString> &referencedFilePaths,
- ProductContext &dummyContext);
- void handleAllPropertyOptionsItems(Item *item);
- void handlePropertyOptions(Item *optionsItem);
-
- using ModuleDependencies = QHash<QualifiedId, QualifiedIdSet>;
- void setupReverseModuleDependencies(const Item::Module &module, ModuleDependencies &deps,
- QualifiedIdSet &seenModules);
- ModuleDependencies setupReverseModuleDependencies(const Item *product);
- void handleGroup(ProductContext *productContext, Item *groupItem,
- const ModuleDependencies &reverseDepencencies);
- void propagateModulesFromParent(ProductContext *productContext, Item *groupItem,
- const ModuleDependencies &reverseDepencencies);
- void adjustDefiningItemsInGroupModuleInstances(const Item::Module &module,
- const Item::Modules &dependentModules);
-
- bool mergeExportItems(const ProductContext &productContext);
- void resolveDependencies(DependsContext *dependsContext, Item *item,
- ProductContext *productContext = nullptr);
- class ItemModuleList;
- void resolveDependsItem(DependsContext *dependsContext, Item *parentItem, Item *dependsItem,
- ItemModuleList *moduleResults, ProductDependencies *productResults);
- void forwardParameterDeclarations(const Item *dependsItem, const ItemModuleList &modules);
- void forwardParameterDeclarations(const QualifiedId &moduleName, Item *item,
- const ItemModuleList &modules);
- void resolveParameterDeclarations(const Item *module);
- QVariantMap extractParameters(Item *dependsItem) const;
- Item *moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName);
- static ProductModuleInfo *productModule(ProductContext *productContext, const QString &name,
- const QString &multiplexId, bool &productNameMatch);
- static ProductContext *product(ProjectContext *projectContext, const QString &name);
- static ProductContext *product(TopLevelProjectContext *tlpContext, const QString &name);
-
- enum class FallbackMode { Enabled, Disabled };
- Item *loadModule(ProductContext *productContext, Item *exportingProductItem, Item *item,
- const CodeLocation &dependsItemLocation, const QString &moduleId,
- const QualifiedId &moduleName, const QString &multiplexId, FallbackMode fallbackMode,
- bool isRequired, bool *isProductDependency, QVariantMap *defaultParameters);
- Item *searchAndLoadModuleFile(ProductContext *productContext,
- const CodeLocation &dependsItemLocation, const QualifiedId &moduleName,
- FallbackMode fallbackMode, bool isRequired, Item *moduleInstance);
- QStringList &getModuleFileNames(const QString &dirPath);
- std::pair<Item *, bool> loadModuleFile(
- ProductContext *productContext, const QString &fullModuleName,
- const QString &filePath, Item *moduleInstance);
- std::pair<Item *, bool> getModulePrototype(ProductContext *productContext,
- const QString &fullModuleName, const QString &filePath);
- Item::Module loadBaseModule(ProductContext *productContext, Item *item);
- void setupBaseModulePrototype(Item *prototype);
- template <typename T, typename F>
- T callWithTemporaryBaseModule(ProductContext *productContext, const F &func);
- void instantiateModule(ProductContext *productContext, Item *exportingProductItem,
- Item *instanceScope, Item *moduleInstance, Item *modulePrototype,
- const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo);
- void createChildInstances(Item *instance, Item *prototype,
- QHash<Item *, Item *> *prototypeInstanceMap) const;
- void checkCancelation() const;
- bool checkItemCondition(Item *item, Item *itemToDisable = nullptr);
- QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr);
- void copyProperties(const Item *sourceProject, Item *targetProject);
- Item *wrapInProjectIfNecessary(Item *item);
- QString findExistingModulePath(const QString &searchPath, const QualifiedId &moduleName);
- QStringList findExistingModulePaths(
- const QStringList &searchPaths, const QualifiedId &moduleName);
-
- static void setScopeForDescendants(Item *item, Item *scope);
- void overrideItemProperties(Item *item, const QString &buildConfigKey,
- const QVariantMap &buildConfig);
- void addProductModuleDependencies(ProductContext *ctx, const QString &name);
- void addProductModuleDependencies(ProductContext *ctx);
- void addTransitiveDependencies(ProductContext *ctx);
- Item *createNonPresentModule(const QString &name, const QString &reason, Item *module);
- void copyGroupsFromModuleToProduct(const ProductContext &productContext,
- const Item::Module &module, const Item *modulePrototype);
- void copyGroupsFromModulesToProduct(const ProductContext &productContext);
- void markModuleTargetGroups(Item *group, const Item::Module &module);
- bool checkExportItemCondition(Item *exportItem, const ProductContext &productContext);
-
- void printProfilingInfo();
- void handleProductError(const ErrorInfo &error, ProductContext *productContext);
- QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group);
- Item *loadItemFromFile(const QString &filePath, const CodeLocation &referencingLocation);
- void collectProductsByName(const TopLevelProjectContext &topLevelProject);
- void collectProductsByType(const TopLevelProjectContext &topLevelProject);
-
- void handleProfileItems(Item *item, ProjectContext *projectContext);
- std::vector<Item *> collectProfileItems(Item *item, ProjectContext *projectContext);
- void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem,
- QVariantMap &values);
- void handleProfile(Item *profileItem);
- void collectNameFromOverride(const QString &overrideString);
- void checkProjectNamesInOverrides(const TopLevelProjectContext &tlp);
- void checkProductNamesInOverrides();
- void setSearchPathsForProduct(ProductContext *product);
-
- Item::Modules modulesSortedByDependency(const Item *productItem);
- void createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules);
- void collectAllModules(Item *item, std::vector<Item::Module> *modules);
- std::vector<Item::Module> allModules(Item *item);
- bool moduleRepresentsDisabledProduct(const Item::Module &module);
-
- using ShadowProductInfo = std::pair<bool, QString>;
- ShadowProductInfo getShadowProductInfo(const ProductContext &product) const;
-
- ItemPool *m_pool;
- Logger &m_logger;
- ProgressObserver *m_progressObserver;
- const std::unique_ptr<ItemReader> m_reader;
- Evaluator *m_evaluator;
- const std::unique_ptr<ProbesResolver> m_probesResolver;
- const std::unique_ptr<ModuleProviderLoader> m_moduleProviderLoader;
- QMap<QString, QStringList> m_moduleDirListCache;
- QHash<std::pair<QString, QualifiedId>, std::optional<QString>> m_existingModulePathCache;
-
- // The keys are file paths, the values are module prototype items accompanied by a profile.
- std::unordered_map<QString, std::vector<std::pair<Item *, QString>>> m_modulePrototypes;
-
- // The keys are module prototypes and products, the values specify whether the module's
- // condition is true for that product.
- QHash<std::pair<Item *, ProductContext *>, bool> m_modulePrototypeEnabledInfo;
-
- QHash<const Item *, Item::PropertyDeclarationMap> m_parameterDeclarations;
- Set<Item *> m_disabledItems;
- std::vector<bool> m_requiredChain;
-
- struct DependsChainEntry
- {
- DependsChainEntry(QualifiedId name, const CodeLocation &location)
- : name(std::move(name)), location(location)
- {
- }
-
- QualifiedId name;
- CodeLocation location;
- bool isProduct = false;
- };
- class DependsChainManager;
- std::vector<DependsChainEntry> m_dependsChain;
-
- FileTime m_lastResolveTime;
- QVariantMap m_storedProfiles;
- QVariantMap m_localProfiles;
- std::multimap<QString, const ProductContext *> m_productsByName;
- std::multimap<FileTag, const ProductContext *> m_productsByType;
-
- std::unordered_map<ProductContext *, Set<DeferredDependsContext>> m_productsWithDeferredDependsItems;
- Set<Item *> m_exportsWithDeferredDependsItems;
-
- SetupProjectParameters m_parameters;
- std::unique_ptr<Settings> m_settings;
- Version m_qbsVersion;
- Item *m_tempScopeItem = nullptr;
-
- qint64 m_elapsedTimeProbes = 0;
- qint64 m_elapsedTimePrepareProducts = 0;
- qint64 m_elapsedTimeProductDependencies = 0;
- qint64 m_elapsedTimeModuleProviders = 0;
- qint64 m_elapsedTimeTransitiveDependencies = 0;
- qint64 m_elapsedTimeHandleProducts = 0;
- qint64 m_elapsedTimePropertyChecking = 0;
- Set<QString> m_projectNamesUsedInOverrides;
- Set<QString> m_productNamesUsedInOverrides;
- Set<QString> m_disabledProjects;
- Set<QString> m_erroneousProducts;
-
- int m_dependencyResolvingPass = 0;
-};
-
-} // namespace Internal
-} // namespace qbs
-
-QT_BEGIN_NAMESPACE
-Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo, Q_MOVABLE_TYPE);
-Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo::Dependency, Q_MOVABLE_TYPE);
-QT_END_NAMESPACE
-
-#endif // QBS_MODULELOADER_H
diff --git a/src/lib/corelib/language/modulemerger.cpp b/src/lib/corelib/language/modulemerger.cpp
deleted file mode 100644
index 6ad8e2259..000000000
--- a/src/lib/corelib/language/modulemerger.cpp
+++ /dev/null
@@ -1,267 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "modulemerger.h"
-
-#include "value.h"
-
-#include <logging/translator.h>
-#include <tools/qbsassert.h>
-#include <tools/qttools.h>
-#include <tools/stlutils.h>
-#include <tools/stringconstants.h>
-
-namespace qbs {
-namespace Internal {
-
-ModuleMerger::ModuleMerger(Logger &logger, Item *productItem, const QString &productName,
- const Item::Modules::iterator &modulesBegin,
- const Item::Modules::iterator &modulesEnd)
- : m_logger(logger)
- , m_productItem(productItem)
- , m_mergedModule(*modulesBegin)
- , m_isBaseModule(m_mergedModule.name.first() == StringConstants::qbsModule())
- , m_isShadowProduct(productName.startsWith(StringConstants::shadowProductPrefix()))
- , m_modulesBegin(std::next(modulesBegin))
- , m_modulesEnd(modulesEnd)
-{
- QBS_CHECK(modulesBegin->item->type() == ItemType::ModuleInstance);
-}
-
-void ModuleMerger::replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace)
-{
- QBS_CHECK(!moduleName.empty());
- QBS_CHECK(containerItem != m_mergedModule.item);
- const QString moduleNamePrefix = moduleName.takeFirst();
- const Item::PropertyMap &properties = containerItem->properties();
- for (auto it = properties.begin(); it != properties.end(); ++it) {
- if (it.key() != moduleNamePrefix)
- continue;
- Value * const val = it.value().get();
- QBS_CHECK(val);
- QBS_CHECK(val->type() == Value::ItemValueType);
- const auto itemVal = static_cast<ItemValue *>(val);
- if (moduleName.empty()) {
- QBS_CHECK(itemVal->item() == toReplace);
- itemVal->setItem(m_mergedModule.item);
- } else {
- replaceItemInValues(moduleName, itemVal->item(), toReplace);
- }
- }
-}
-
-void ModuleMerger::start()
-{
- // Iterate over any module that our product depends on. These modules
- // may depend on m_mergedModule and contribute property assignments.
- Item::PropertyMap props;
- for (auto module = m_modulesBegin; module != m_modulesEnd; module++)
- mergeModule(&props, *module);
-
- // Module property assignments in the product have the highest priority
- // and are thus prepended.
- Item::Module m;
- m.item = m_productItem;
- mergeModule(&props, m);
-
- // The module's prototype is the essential unmodified module as loaded
- // from the cache.
- Item *moduleProto = m_mergedModule.item->prototype();
- while (moduleProto->prototype())
- moduleProto = moduleProto->prototype();
-
- // The prototype item might contain default values which get appended in
- // case of list properties. Scalar properties will only be set if not
- // already specified above.
- Item::PropertyMap mergedProps = m_mergedModule.item->properties();
- for (auto it = props.constBegin(); it != props.constEnd(); ++it) {
- appendPrototypeValueToNextChain(moduleProto, it.key(), it.value());
- mergedProps[it.key()] = it.value();
- }
-
- m_mergedModule.item->setProperties(mergedProps);
-
- // Update all sibling instances of the to-be-merged module to behave identical
- // to the merged module.
- for (Item *moduleInstanceContainer : qAsConst(m_moduleInstanceContainers)) {
- Item::Modules modules;
- for (const Item::Module &dep : moduleInstanceContainer->modules()) {
- const bool isTheModule = dep.name == m_mergedModule.name;
- Item::Module m = dep;
- if (isTheModule && m.item != m_mergedModule.item) {
- QBS_CHECK(m.item->type() == ItemType::ModuleInstance);
- replaceItemInValues(m.name, moduleInstanceContainer, m.item);
- m.item = m_mergedModule.item;
- m.required = m_mergedModule.required;
- m.versionRange = m_mergedModule.versionRange;
- m.fallbackEnabled = m_mergedModule.fallbackEnabled;
- }
- modules << m;
- }
- moduleInstanceContainer->setModules(modules);
- }
-}
-
-void ModuleMerger::mergeModule(Item::PropertyMap *dstProps, const Item::Module &module)
-{
- const Item::Module *dep = findModule(module.item, m_mergedModule.name);
- if (!dep)
- return;
-
- const bool mergingProductItem = (module.item == m_productItem);
- Item *srcItem = dep->item;
- Item *origSrcItem = srcItem;
- do {
- if (m_seenInstances.insert(srcItem).second) {
- for (auto it = srcItem->properties().constBegin();
- it != srcItem->properties().constEnd(); ++it) {
- const ValuePtr &srcVal = it.value();
- if (srcVal->type() == Value::ItemValueType)
- continue;
- if (it.key() == StringConstants::qbsSourceDirPropertyInternal())
- continue;
- const PropertyDeclaration srcDecl = srcItem->propertyDeclaration(it.key());
- if (!srcDecl.isValid())
- continue;
-
- // Scalar variant values could stem from product multiplexing, in which case
- // the merged qbs module instance needs to get that value.
- if (srcVal->type() == Value::VariantValueType
- && (!srcDecl.isScalar() || !m_isBaseModule)) {
- continue;
- }
-
- ValuePtr clonedSrcVal = srcVal->clone();
- clonedSrcVal->setDefiningItem(origSrcItem);
-
- ValuePtr &dstVal = (*dstProps)[it.key()];
- if (dstVal) {
- if (srcDecl.isScalar()) {
- // Scalar properties get replaced.
- if ((dstVal->type() == Value::JSSourceValueType)
- && (srcVal->type() == Value::JSSourceValueType)) {
- // Warn only about conflicting source code values
- const JSSourceValuePtr dstJsVal =
- std::static_pointer_cast<JSSourceValue>(dstVal);
- const JSSourceValuePtr srcJsVal =
- std::static_pointer_cast<JSSourceValue>(srcVal);
- const bool overriddenInProduct =
- m_mergedModule.item->properties().contains(it.key());
-
- if (dstJsVal->sourceCode() != srcJsVal->sourceCode()
- && !mergingProductItem && !overriddenInProduct
- && !m_isShadowProduct) {
- m_logger.qbsWarning()
- << Tr::tr("Conflicting scalar values at %1 and %2.").arg(
- dstJsVal->location().toString(),
- srcJsVal->location().toString());
- }
- }
- } else {
- // List properties get prepended
- QBS_CHECK(!clonedSrcVal->next());
- clonedSrcVal->setNext(dstVal);
- }
- }
- dstVal = clonedSrcVal;
- }
- }
- srcItem = srcItem->prototype();
- } while (srcItem && srcItem->type() == ItemType::ModuleInstance);
-
- // Update dependency constraints
- if (dep->required)
- m_mergedModule.required = true;
- // if one dep has fallback disabled, disable it for the merged module
- m_mergedModule.fallbackEnabled = m_mergedModule.fallbackEnabled && dep->fallbackEnabled;
- m_mergedModule.versionRange.narrowDown(dep->versionRange);
-
- // We need to touch the unmerged module instances later once more
- m_moduleInstanceContainers << module.item;
-}
-
-void ModuleMerger::appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName,
- const ValuePtr &sv)
-{
- const PropertyDeclaration pd = m_mergedModule.item->propertyDeclaration(propertyName);
- if (pd.isScalar())
- return;
- if (!m_clonedModulePrototype) {
- m_clonedModulePrototype = Item::create(moduleProto->pool(), ItemType::Module);
- m_clonedModulePrototype->setScope(m_mergedModule.item);
- m_clonedModulePrototype->setLocation(moduleProto->location());
- moduleProto->copyProperty(StringConstants::nameProperty(), m_clonedModulePrototype);
- }
- const ValuePtr &protoValue = moduleProto->property(propertyName);
- QBS_CHECK(protoValue);
- const ValuePtr clonedValue = protoValue->clone();
- lastInNextChain(sv)->setNext(clonedValue);
- clonedValue->setDefiningItem(m_clonedModulePrototype);
- m_clonedModulePrototype->setPropertyDeclaration(propertyName, pd);
- m_clonedModulePrototype->setProperty(propertyName, clonedValue);
-}
-
-ValuePtr ModuleMerger::lastInNextChain(const ValuePtr &v)
-{
- ValuePtr n = v;
- while (n->next())
- n = n->next();
- return n;
-}
-
-const Item::Module *ModuleMerger::findModule(const Item *item, const QualifiedId &name)
-{
- for (const auto &module : item->modules()) {
- if (module.name == name)
- return &module;
- }
- return nullptr;
-}
-
-void ModuleMerger::merge(Logger &logger, Item *product, const QString &productName,
- Item::Modules *topSortedModules)
-{
- for (auto it = topSortedModules->begin(); it != topSortedModules->end(); ++it)
- ModuleMerger(logger, product, productName, it, topSortedModules->end()).start();
-}
-
-
-
-} // namespace Internal
-} // namespace qbs
diff --git a/src/lib/corelib/language/moduleproviderinfo.h b/src/lib/corelib/language/moduleproviderinfo.h
index d4c375f4a..500d370cc 100644
--- a/src/lib/corelib/language/moduleproviderinfo.h
+++ b/src/lib/corelib/language/moduleproviderinfo.h
@@ -90,8 +90,9 @@ public:
using ModuleProviderInfoList = std::vector<ModuleProviderInfo>;
// Persistent info stored between sessions
-struct StoredModuleProviderInfo
+class StoredModuleProviderInfo
{
+public:
using CacheKey = std::tuple<
QString /*name*/,
QVariantMap /*config*/,
diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h
deleted file mode 100644
index 52d835535..000000000
--- a/src/lib/corelib/language/projectresolver.h
+++ /dev/null
@@ -1,208 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of Qbs.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef PROJECTRESOLVER_H
-#define PROJECTRESOLVER_H
-
-#include "filetags.h"
-#include "itemtype.h"
-#include "moduleloader.h"
-#include "qualifiedid.h"
-
-#include <logging/logger.h>
-#include <tools/set.h>
-
-#include <QtCore/qhash.h>
-#include <QtCore/qmap.h>
-#include <QtCore/qstringlist.h>
-
-#include <utility>
-#include <vector>
-
-namespace qbs {
-class JobLimits;
-namespace Internal {
-
-class Evaluator;
-class Item;
-class ProgressObserver;
-class ScriptEngine;
-
-class ProjectResolver
-{
-public:
- ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult,
- SetupProjectParameters setupParameters, Logger &logger);
- ~ProjectResolver();
-
- void setProgressObserver(ProgressObserver *observer);
- TopLevelProjectPtr resolve();
-
- static void applyFileTaggers(const SourceArtifactPtr &artifact,
- const ResolvedProductConstPtr &product);
-
- using FileLocations = QHash<std::pair<QString, QString>, CodeLocation>;
- static SourceArtifactPtr createSourceArtifact(const ResolvedProductPtr &rproduct,
- const QString &fileName, const GroupPtr &group, bool wildcard,
- const CodeLocation &filesLocation = CodeLocation(),
- FileLocations *fileLocations = nullptr, ErrorInfo *errorInfo = nullptr);
-
-private:
- struct ProjectContext;
- struct ProductContext;
- struct ModuleContext;
- class ProductContextSwitcher;
-
- void checkCancelation() const;
- QString verbatimValue(const ValueConstPtr &value, bool *propertyWasSet = nullptr) const;
- QString verbatimValue(Item *item, const QString &name, bool *propertyWasSet = nullptr) const;
- ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const;
- ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const;
- void ignoreItem(Item *item, ProjectContext *projectContext);
- TopLevelProjectPtr resolveTopLevelProject();
- void resolveProject(Item *item, ProjectContext *projectContext);
- void resolveProjectFully(Item *item, ProjectContext *projectContext);
- void resolveSubProject(Item *item, ProjectContext *projectContext);
- void resolveProduct(Item *item, ProjectContext *projectContext);
- void resolveProductFully(Item *item, ProjectContext *projectContext);
- void resolveModules(const Item *item, ProjectContext *projectContext);
- void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
- const QVariantMap &parameters, JobLimits &jobLimits,
- ProjectContext *projectContext);
- void gatherProductTypes(ResolvedProduct *product, Item *item);
- QVariantMap resolveAdditionalModuleProperties(const Item *group,
- const QVariantMap &currentValues);
- void resolveGroup(Item *item, ProjectContext *projectContext);
- void resolveGroupFully(Item *item, ProjectContext *projectContext, bool isEnabled);
- void resolveShadowProduct(Item *item, ProjectContext *);
- void resolveExport(Item *exportItem, ProjectContext *);
- std::unique_ptr<ExportedItem> resolveExportChild(const Item *item,
- const ExportedModule &module);
- void resolveRule(Item *item, ProjectContext *projectContext);
- void resolveRuleArtifact(const RulePtr &rule, Item *item);
- void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item,
- const QStringList &namePrefix,
- QualifiedIdSet *seenBindings);
- void resolveFileTagger(Item *item, ProjectContext *projectContext);
- void resolveJobLimit(Item *item, ProjectContext *projectContext);
- void resolveScanner(Item *item, ProjectContext *projectContext);
- void resolveProductDependencies(const ProjectContext &projectContext);
- void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const;
- void applyFileTaggers(const ResolvedProductPtr &product) const;
- QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true);
- QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors);
- QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer,
- const QVariantMap &tmplt, bool lookupPrototype,
- bool checkErrors);
- void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue,
- QVariantMap &result, bool checkErrors);
- void checkAllowedValues(
- const QVariant &v, const CodeLocation &loc, const PropertyDeclaration &decl,
- const QString &key) const;
- void createProductConfig(ResolvedProduct *product);
- ProjectContext createProjectContext(ProjectContext *parentProjectContext) const;
- void adaptExportedPropertyValues(const Item *shadowProductItem);
- void collectExportedProductDependencies();
-
- struct ProductDependencyInfo
- {
- ProductDependencyInfo(ResolvedProductPtr product,
- QVariantMap parameters = QVariantMap())
- : product(std::move(product)), parameters(std::move(parameters))
- {
- }
-
- ResolvedProductPtr product;
- QVariantMap parameters;
- };
-
- struct ProductDependencyInfos
- {
- std::vector<ProductDependencyInfo> dependencies;
- bool hasDisabledDependency = false;
- };
-
- ProductDependencyInfos getProductDependencies(const ResolvedProductConstPtr &product,
- const ModuleLoaderResult::ProductInfo &productInfo);
- QString sourceCodeAsFunction(const JSSourceValueConstPtr &value,
- const PropertyDeclaration &decl) const;
- QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const;
- static void matchArtifactProperties(const ResolvedProductPtr &product,
- const std::vector<SourceArtifactPtr> &artifacts);
- void printProfilingInfo();
-
- void collectPropertiesForExportItem(Item *productModuleInstance);
- void collectPropertiesForModuleInExportItem(const Item::Module &module);
-
- void collectPropertiesForExportItem(const QualifiedId &moduleName,
- const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps);
- void setupExportedProperties(const Item *item, const QString &namePrefix,
- std::vector<ExportedProperty> &properties);
-
- Evaluator *m_evaluator = nullptr;
- Logger &m_logger;
- ScriptEngine *m_engine = nullptr;
- ProgressObserver *m_progressObserver = nullptr;
- ProductContext *m_productContext = nullptr;
- ModuleContext *m_moduleContext = nullptr;
- QMap<QString, ResolvedProductPtr> m_productsByName;
- QHash<FileTag, QList<ResolvedProductPtr> > m_productsByType;
- QHash<ResolvedProductPtr, Item *> m_productItemMap;
- mutable QHash<FileContextConstPtr, ResolvedFileContextPtr> m_fileContextMap;
- mutable QHash<CodeLocation, ScriptFunctionPtr> m_scriptFunctionMap;
- mutable QHash<std::pair<QStringView, QStringList>, QString> m_scriptFunctions;
- mutable QHash<QStringView, QString> m_sourceCode;
- const SetupProjectParameters m_setupParams;
- ModuleLoaderResult m_loadResult;
- Set<CodeLocation> m_groupLocationWarnings;
- std::vector<std::pair<ResolvedProductPtr, Item *>> m_productExportInfo;
- std::vector<ErrorInfo> m_queuedErrors;
- qint64 m_elapsedTimeModPropEval = 0;
- qint64 m_elapsedTimeAllPropEval = 0;
- qint64 m_elapsedTimeGroups = 0;
-
- typedef void (ProjectResolver::*ItemFuncPtr)(Item *item, ProjectContext *projectContext);
- using ItemFuncMap = QMap<ItemType, ItemFuncPtr>;
- void callItemFunction(const ItemFuncMap &mappings, Item *item, ProjectContext *projectContext);
-};
-
-} // namespace Internal
-} // namespace qbs
-
-#endif // PROJECTRESOLVER_H
diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp
index f2ac019e9..215918462 100644
--- a/src/lib/corelib/language/propertydeclaration.cpp
+++ b/src/lib/corelib/language/propertydeclaration.cpp
@@ -40,11 +40,15 @@
#include "propertydeclaration.h"
#include "deprecationinfo.h"
-#include <api/languageinfo.h>
+#include "filecontext.h"
+#include "item.h"
+#include "qualifiedid.h"
+#include "value.h"
+#include <api/languageinfo.h>
#include <logging/translator.h>
-
#include <tools/error.h>
+#include <tools/setupprojectparameters.h>
#include <tools/qttools.h>
#include <tools/stringconstants.h>
@@ -304,5 +308,142 @@ QVariant PropertyDeclaration::convertToPropertyType(const QVariant &v, Type t,
return c;
}
+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); }
+
+private:
+ void handle(JSSourceValue *value) override
+ {
+ if (!value->createdByPropertiesBlock()) {
+ const ErrorInfo error(Tr::tr("Property '%1' is not declared.")
+ .arg(m_currentName), value->location());
+ handlePropertyError(error, m_params, m_logger);
+ }
+ }
+ void handle(ItemValue *value) override
+ {
+ if (checkItemValue(value))
+ handleItem(value->item());
+ }
+ bool checkItemValue(ItemValue *value)
+ {
+ // TODO: Remove once QBS-1030 is fixed.
+ if (parentItem()->type() == ItemType::Artifact)
+ return false;
+
+ if (parentItem()->type() == ItemType::Properties)
+ return false;
+
+ // TODO: Check where the in-between module instances come from.
+ if (value->item()->type() == ItemType::ModuleInstancePlaceholder) {
+ for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) {
+ if ((*it)->type() == ItemType::Group)
+ return false;
+ if ((*it)->type() == ItemType::ModulePrefix)
+ continue;
+ break;
+ }
+ }
+
+ if (value->item()->type() != ItemType::ModuleInstance
+ && value->item()->type() != ItemType::ModulePrefix
+ && (!parentItem()->file() || !parentItem()->file()->idScope()
+ || !parentItem()->file()->idScope()->hasProperty(m_currentName))
+ && !value->createdByPropertiesBlock()) {
+ CodeLocation location = value->location();
+ for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i)
+ location = m_parentItems.at(i)->location();
+ const ErrorInfo error(Tr::tr("Item '%1' is not declared. "
+ "Did you forget to add a Depends item?")
+ .arg(m_currentModuleName.toString()), location);
+ handlePropertyError(error, m_params, m_logger);
+ return false;
+ }
+
+ return true;
+ }
+ void handleItem(Item *item)
+ {
+ if (!m_handledItems.insert(item).second)
+ return;
+ if (m_disabledItems.contains(item)
+ || item->type() == ItemType::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) {
+ return;
+ }
+
+ m_parentItems.push_back(item);
+ for (Item::PropertyMap::const_iterator it = item->properties().constBegin();
+ it != item->properties().constEnd(); ++it) {
+ if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders()
+ && it.value()->type() == Value::ItemValueType)
+ continue;
+ const PropertyDeclaration decl = item->propertyDeclaration(it.key());
+ if (decl.isValid()) {
+ const ErrorInfo deprecationError = decl.checkForDeprecation(
+ m_params.deprecationWarningMode(), it.value()->location(), m_logger);
+ if (deprecationError.hasError())
+ handlePropertyError(deprecationError, m_params, m_logger);
+ continue;
+ }
+ m_currentName = it.key();
+ const QualifiedId oldModuleName = m_currentModuleName;
+ if (parentItem()->type() != ItemType::ModulePrefix)
+ m_currentModuleName.clear();
+ m_currentModuleName.push_back(m_currentName);
+ it.value()->apply(this);
+ m_currentModuleName = oldModuleName;
+ }
+ m_parentItems.pop_back();
+ for (Item * const child : item->children()) {
+ switch (child->type()) {
+ case ItemType::Export:
+ case ItemType::Depends:
+ case ItemType::Parameter:
+ case ItemType::Parameters:
+ break;
+ case ItemType::Group:
+ if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance)
+ break;
+ Q_FALLTHROUGH();
+ default:
+ handleItem(child);
+ }
+ }
+ }
+ void handle(VariantValue *) override { /* only created internally - no need to check */ }
+
+ Item *parentItem() const { return m_parentItems.back(); }
+
+ const Set<Item *> &m_disabledItems;
+ Set<Item *> m_handledItems;
+ std::vector<Item *> m_parentItems;
+ QualifiedId m_currentModuleName;
+ QString m_currentName;
+ const SetupProjectParameters &m_params;
+ Logger &m_logger;
+};
+} // namespace
+
+void checkPropertyDeclarations(Item *topLevelItem, const Set<Item *> &disabledItems,
+ const SetupProjectParameters &params, Logger &logger)
+{
+ PropertyDeclarationCheck(disabledItems, params, logger)(topLevelItem);
+}
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h
index d1e114296..8c87faedb 100644
--- a/src/lib/corelib/language/propertydeclaration.h
+++ b/src/lib/corelib/language/propertydeclaration.h
@@ -41,6 +41,7 @@
#define QBS_PROPERTYDECLARATION_H
#include <tools/deprecationwarningmode.h>
+#include <tools/set.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qstring.h>
@@ -52,9 +53,11 @@ QT_END_NAMESPACE
namespace qbs {
class CodeLocation;
class ErrorInfo;
+class SetupProjectParameters;
namespace Internal {
class DeprecationInfo;
class PropertyDeclarationData;
+class Item;
class Logger;
class PropertyDeclaration
@@ -131,6 +134,10 @@ private:
QSharedDataPointer<PropertyDeclarationData> d;
};
+void checkPropertyDeclarations(Item *topLevelItem, const Set<Item *> &disabledItems,
+ const SetupProjectParameters &params, Logger &logger);
+
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp
index a8e6cb657..11d41b5c2 100644
--- a/src/lib/corelib/language/scriptengine.cpp
+++ b/src/lib/corelib/language/scriptengine.cpp
@@ -135,17 +135,10 @@ LookupResult ScriptEngine::doExtraScopeLookup(JSContext *ctx, JSAtom prop)
ScriptEngine * const engine = engineForContext(ctx);
engine->m_lastLookupWasSuccess = false;
- JSValueList scopes;
-
- // FIXME: This is probably wrong, and kept only for "bug compatibility"
- // The correct code should be the one commented out below. Fix for 2.1.
- for (const auto &l : qAsConst(engine->m_scopeChains)) {
- for (const auto &s : l)
- scopes.push_back(s);
- }
- // if (!engine->m_scopeChains.isEmpty())
- // scopes = engine->m_scopeChains.last();
+ JSValueList scopes;
+ if (!engine->m_scopeChains.isEmpty())
+ scopes = engine->m_scopeChains.last();
if (JS_IsObject(engine->m_globalObject))
scopes.insert(scopes.begin(), engine->m_globalObject);
for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) {
@@ -181,9 +174,9 @@ ScriptEngine::~ScriptEngine()
m_logger.qbsLog(LoggerInfo, true) << Tr::tr("Setting up imports took %1.")
.arg(elapsedTimeString(m_elapsedTimeImporting));
}
- for (const auto &ext : qAsConst(m_internalExtensions))
+ for (const auto &ext : std::as_const(m_internalExtensions))
JS_FreeValue(m_context, ext);
- for (const JSValue &s : qAsConst(m_stringCache))
+ for (const JSValue &s : std::as_const(m_stringCache))
JS_FreeValue(m_context, s);
for (JSValue * const externalRef : std::as_const(m_externallyCachedValues)) {
JS_FreeValue(m_context, *externalRef);
@@ -199,7 +192,7 @@ void ScriptEngine::reset()
// TODO: Check whether we can keep file and imports cache.
// We'd have to find a solution for the scope name problem then.
clearImportsCache();
- for (const auto &e : qAsConst(m_jsFileCache))
+ for (const auto &e : std::as_const(m_jsFileCache))
JS_FreeValue(m_context, e.second);
m_jsFileCache.clear();
@@ -208,19 +201,19 @@ void ScriptEngine::reset()
JS_FreeValue(m_context, it.key());
}
m_evalResults.clear();
- for (const auto &e : qAsConst(m_projectScriptValues))
+ for (const auto &e : std::as_const(m_projectScriptValues))
JS_FreeValue(m_context, e.second);
m_projectScriptValues.clear();
- for (const auto &e : qAsConst(m_baseProductScriptValues))
+ for (const auto &e : std::as_const(m_baseProductScriptValues))
JS_FreeValue(m_context, e.second);
m_baseProductScriptValues.clear();
- for (const auto &e : qAsConst(m_productArtifactsMapScriptValues))
+ for (const auto &e : std::as_const(m_productArtifactsMapScriptValues))
JS_FreeValue(m_context, e.second);
m_productArtifactsMapScriptValues.clear();
- for (const auto &e : qAsConst(m_moduleArtifactsMapScriptValues))
+ for (const auto &e : std::as_const(m_moduleArtifactsMapScriptValues))
JS_FreeValue(m_context, e.second);
m_moduleArtifactsMapScriptValues.clear();
- for (const auto &e : qAsConst(m_baseModuleScriptValues))
+ for (const auto &e : std::as_const(m_baseModuleScriptValues))
JS_FreeValue(m_context, e.second);
m_baseModuleScriptValues.clear();
for (auto it = m_artifactsScriptValues.cbegin(); it != m_artifactsScriptValues.cend(); ++it) {
@@ -286,7 +279,7 @@ void ScriptEngine::observeImport(JSValue &jsImport)
void ScriptEngine::clearImportsCache()
{
- for (const auto &jsImport : qAsConst(m_jsImportCache))
+ for (const auto &jsImport : std::as_const(m_jsImportCache))
JS_FreeValue(m_context, jsImport);
m_jsImportCache.clear();
m_filePathsPerImport.clear();
@@ -488,6 +481,12 @@ void ScriptEngine::addInternalExtension(const char *name, JSValue ext)
m_internalExtensions.insert(QLatin1String(name), JS_DupValue(m_context, ext));
}
+JSValue ScriptEngine::asJsValue(const QByteArray &s)
+{
+ return JS_NewArrayBufferCopy(
+ m_context, reinterpret_cast<const uint8_t *>(s.constData()), s.size());
+}
+
JSValue ScriptEngine::asJsValue(const QString &s)
{
const auto it = m_stringCache.constFind(s);
diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h
index 8c97e3079..4d797dd43 100644
--- a/src/lib/corelib/language/scriptengine.h
+++ b/src/lib/corelib/language/scriptengine.h
@@ -283,6 +283,7 @@ public:
JSValue getInternalExtension(const char *name) const;
void addInternalExtension(const char *name, JSValue ext);
+ JSValue asJsValue(const QByteArray &s);
JSValue asJsValue(const QString &s);
JSValue asJsValue(const QStringList &l);
JSValue asJsValue(const QVariantList &l);
diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp
index 5a4da2c8f..fabc64ccd 100644
--- a/src/lib/corelib/language/value.cpp
+++ b/src/lib/corelib/language/value.cpp
@@ -48,29 +48,65 @@
namespace qbs {
namespace Internal {
-Value::Value(Type t, bool createdByPropertiesBlock)
- : m_type(t), m_definingItem(nullptr), m_createdByPropertiesBlock(createdByPropertiesBlock)
+Value::Value(Type t, bool createdByPropertiesBlock) : m_type(t)
{
+ if (createdByPropertiesBlock)
+ m_flags |= OriginPropertiesBlock;
}
Value::Value(const Value &other)
: m_type(other.m_type),
- m_definingItem(other.m_definingItem),
+ m_scope(other.m_scope),
+ m_scopeName(other.m_scopeName),
m_next(other.m_next ? other.m_next->clone() : ValuePtr()),
- m_createdByPropertiesBlock(other.m_createdByPropertiesBlock)
+ m_candidates(other.m_candidates),
+ m_flags(other.m_flags)
{
}
Value::~Value() = default;
-Item *Value::definingItem() const
+void Value::setScope(Item *scope, const QString &scopeName)
{
- return m_definingItem;
+ m_scope = scope;
+ m_scopeName = scopeName;
}
-void Value::setDefiningItem(Item *item)
+int Value::priority(const Item *productItem) const
{
- m_definingItem = item;
+ if (m_priority == -1)
+ m_priority = calculatePriority(productItem);
+ return m_priority;
+}
+
+int Value::calculatePriority(const Item *productItem) const
+{
+ if (setInternally())
+ return INT_MAX;
+ if (setByCommandLine())
+ return INT_MAX - 1;
+ if (setByProfile())
+ return 2;
+ if (!scope())
+ return 1;
+ if (scope()->type() == ItemType::Product)
+ return INT_MAX - 2;
+ if (!scope()->isPresentModule())
+ return 0;
+ const auto it = std::find_if(
+ productItem->modules().begin(), productItem->modules().end(),
+ [this](const Item::Module &m) { return m.item == scope(); });
+ QBS_CHECK(it != productItem->modules().end());
+ return INT_MAX - 3 - it->maxDependsChainLength;
+}
+
+void Value::resetPriority()
+{
+ m_priority = -1;
+ if (m_next)
+ m_next->resetPriority();
+ for (const ValuePtr &v : m_candidates)
+ v->resetPriority();
}
ValuePtr Value::next() const
@@ -85,6 +121,11 @@ void Value::setNext(const ValuePtr &next)
m_next = next;
}
+bool Value::setInternally() const
+{
+ return type() == VariantValueType && !setByProfile() && !setByCommandLine();
+}
+
JSSourceValue::JSSourceValue(bool createdByPropertiesBlock)
: Value(JSSourceValueType, createdByPropertiesBlock)
@@ -99,7 +140,6 @@ JSSourceValue::JSSourceValue(const JSSourceValue &other) : Value(other)
m_line = other.m_line;
m_column = other.m_column;
m_file = other.m_file;
- m_flags = other.m_flags;
m_baseValue = other.m_baseValue
? std::static_pointer_cast<JSSourceValue>(other.m_baseValue->clone())
: JSSourceValuePtr();
@@ -142,24 +182,45 @@ CodeLocation JSSourceValue::location() const
return CodeLocation(m_file->filePath(), m_line, m_column);
}
-void JSSourceValue::setHasFunctionForm(bool b)
+void JSSourceValue::clearAlternatives()
+{
+ m_alternatives.clear();
+}
+
+void JSSourceValue::setScope(Item *scope, const QString &scopeName)
{
- if (b)
- m_flags |= HasFunctionForm;
- else
- m_flags &= ~HasFunctionForm;
+ Value::setScope(scope, scopeName);
+ if (m_baseValue)
+ m_baseValue->setScope(scope, scopeName);
+ for (const JSSourceValue::Alternative &a : m_alternatives)
+ a.value->setScope(scope, scopeName);
}
-void JSSourceValue::clearAlternatives()
+void JSSourceValue::resetPriority()
{
- m_alternatives.clear();
+ Value::resetPriority();
+ if (m_baseValue)
+ m_baseValue->resetPriority();
+ for (const JSSourceValue::Alternative &a : m_alternatives)
+ a.value->resetPriority();
+}
+
+void JSSourceValue::addCandidate(const ValuePtr &v)
+{
+ Value::addCandidate(v);
+ if (m_baseValue)
+ m_baseValue->addCandidate(v);
+ for (const JSSourceValue::Alternative &a : m_alternatives)
+ a.value->addCandidate(v);
}
-void JSSourceValue::setDefiningItem(Item *item)
+void JSSourceValue::setCandidates(const std::vector<ValuePtr> &candidates)
{
- Value::setDefiningItem(item);
+ Value::setCandidates(candidates);
+ if (m_baseValue)
+ m_baseValue->setCandidates(candidates);
for (const JSSourceValue::Alternative &a : m_alternatives)
- a.value->setDefiningItem(item);
+ a.value->setCandidates(candidates);
}
ItemValue::ItemValue(Item *item, bool createdByPropertiesBlock)
diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h
index 5f6b5ca16..262813841 100644
--- a/src/lib/corelib/language/value.h
+++ b/src/lib/corelib/language/value.h
@@ -42,6 +42,7 @@
#include "forward_decls.h"
#include <tools/codelocation.h>
+#include <QtCore/qstring.h>
#include <QtCore/qvariant.h>
#include <vector>
@@ -54,13 +55,26 @@ class ValueHandler;
class Value
{
public:
- enum Type
- {
+ enum Type {
JSSourceValueType,
ItemValueType,
VariantValueType
};
+ enum Flag {
+ NoFlags = 0x00,
+ SourceUsesBase = 0x01,
+ SourceUsesOuter = 0x02,
+ SourceUsesOriginal = 0x04,
+ HasFunctionForm = 0x08,
+ ExclusiveListValue = 0x10,
+ BuiltinDefaultValue = 0x20,
+ OriginPropertiesBlock = 0x40,
+ OriginProfile = 0x80,
+ OriginCommandLine = 0x100,
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
Value(Type t, bool createdByPropertiesBlock);
Value(const Value &other);
virtual ~Value();
@@ -70,21 +84,50 @@ public:
virtual ValuePtr clone() const = 0;
virtual CodeLocation location() const { return {}; }
- Item *definingItem() const;
- virtual void setDefiningItem(Item *item);
+ Item *scope() const { return m_scope; }
+ virtual void setScope(Item *scope, const QString &scopeName);
+ QString scopeName() const { return m_scopeName; }
+ int priority(const Item *productItem) const;
+ virtual void resetPriority();
ValuePtr next() const;
void setNext(const ValuePtr &next);
- bool createdByPropertiesBlock() const { return m_createdByPropertiesBlock; }
- void setCreatedByPropertiesBlock(bool b) { m_createdByPropertiesBlock = b; }
- void clearCreatedByPropertiesBlock() { m_createdByPropertiesBlock = false; }
+ virtual void addCandidate(const ValuePtr &v) { m_candidates.push_back(v); }
+ const std::vector<ValuePtr> &candidates() const { return m_candidates; }
+ virtual void setCandidates(const std::vector<ValuePtr> &candidates) { m_candidates = candidates; }
+
+ bool createdByPropertiesBlock() const { return m_flags & OriginPropertiesBlock; }
+ void markAsSetByProfile() { m_flags |= OriginProfile; }
+ bool setByProfile() const { return m_flags & OriginProfile; }
+ void markAsSetByCommandLine() { m_flags |= OriginCommandLine; }
+ bool setByCommandLine() const { return m_flags & OriginCommandLine; }
+ bool setInternally() const;
+ bool expired(const Item *productItem) const { return priority(productItem) == 0; }
+
+ void setSourceUsesBase() { m_flags |= SourceUsesBase; }
+ bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); }
+ void setSourceUsesOuter() { m_flags |= SourceUsesOuter; }
+ bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); }
+ void setSourceUsesOriginal() { m_flags |= SourceUsesOriginal; }
+ bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); }
+ void setHasFunctionForm() { m_flags |= HasFunctionForm; }
+ bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); }
+ void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; }
+ bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); }
+ void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; }
+ bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); }
private:
+ int calculatePriority(const Item *productItem) const;
+
Type m_type;
- Item *m_definingItem;
+ Item *m_scope = nullptr;
+ QString m_scopeName;
ValuePtr m_next;
- bool m_createdByPropertiesBlock;
+ std::vector<ValuePtr> m_candidates;
+ Flags m_flags;
+ mutable int m_priority = -1;
};
class ValueHandler
@@ -99,18 +142,6 @@ class JSSourceValue : public Value
{
friend class ItemReaderASTVisitor;
- enum Flag
- {
- NoFlags = 0x00,
- SourceUsesBase = 0x01,
- SourceUsesOuter = 0x02,
- SourceUsesOriginal = 0x04,
- HasFunctionForm = 0x08,
- ExclusiveListValue = 0x10,
- BuiltinDefaultValue = 0x20,
- };
- Q_DECLARE_FLAGS(Flags, Flag)
-
public:
explicit JSSourceValue(bool createdByPropertiesBlock);
JSSourceValue(const JSSourceValue &other);
@@ -133,17 +164,6 @@ public:
void setFile(const FileContextPtr &file) { m_file = file; }
const FileContextPtr &file() const { return m_file; }
- void setSourceUsesBaseFlag() { m_flags |= SourceUsesBase; }
- bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); }
- bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); }
- bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); }
- bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); }
- void setHasFunctionForm(bool b);
- void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; }
- bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); }
- void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; }
- bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); }
-
const JSSourceValuePtr &baseValue() const { return m_baseValue; }
void setBaseValue(const JSSourceValuePtr &v) { m_baseValue = v; }
@@ -176,14 +196,16 @@ public:
void addAlternative(const Alternative &alternative) { m_alternatives.push_back(alternative); }
void clearAlternatives();
- void setDefiningItem(Item *item) override;
+ void setScope(Item *scope, const QString &scopeName) override;
+ void resetPriority() override;
+ void addCandidate(const ValuePtr &v) override;
+ void setCandidates(const std::vector<ValuePtr> &candidates) override;
private:
QStringView m_sourceCode;
int m_line;
int m_column;
FileContextPtr m_file;
- Flags m_flags;
JSSourceValuePtr m_baseValue;
std::vector<Alternative> m_alternatives;
};
diff --git a/src/lib/corelib/language/astimportshandler.cpp b/src/lib/corelib/loader/astimportshandler.cpp
index d634af7e4..90887e728 100644
--- a/src/lib/corelib/language/astimportshandler.cpp
+++ b/src/lib/corelib/loader/astimportshandler.cpp
@@ -38,12 +38,12 @@
****************************************************************************/
#include "astimportshandler.h"
-#include "asttools.h"
-#include "builtindeclarations.h"
-#include "filecontext.h"
#include "itemreadervisitorstate.h"
-#include "jsextensions/jsextensions.h"
+#include <jsextensions/jsextensions.h>
+#include <language/asttools.h>
+#include <language/builtindeclarations.h>
+#include <language/filecontext.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <parser/qmljsast_p.h>
@@ -269,7 +269,7 @@ void ASTImportsHandler::collectPrototypes(const QString &path, const QString &as
{
QStringList fileNames; // Yes, file *names*.
if (m_visitorState.findDirectoryEntries(path, &fileNames)) {
- for (const QString &fileName : qAsConst(fileNames))
+ for (const QString &fileName : std::as_const(fileNames))
addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false);
return;
}
diff --git a/src/lib/corelib/language/astimportshandler.h b/src/lib/corelib/loader/astimportshandler.h
index e9c2b6c27..582e1c698 100644
--- a/src/lib/corelib/language/astimportshandler.h
+++ b/src/lib/corelib/loader/astimportshandler.h
@@ -39,7 +39,7 @@
#ifndef QBS_ASTIMPORTSHANDLER_H
#define QBS_ASTIMPORTSHANDLER_H
-#include "forward_decls.h"
+#include <language/forward_decls.h>
#include <parser/qmljsastfwd_p.h>
#include <tools/set.h>
diff --git a/src/lib/corelib/language/astpropertiesitemhandler.cpp b/src/lib/corelib/loader/astpropertiesitemhandler.cpp
index b28fe7d76..fbb8761b5 100644
--- a/src/lib/corelib/language/astpropertiesitemhandler.cpp
+++ b/src/lib/corelib/loader/astpropertiesitemhandler.cpp
@@ -38,8 +38,8 @@
****************************************************************************/
#include "astpropertiesitemhandler.h"
-#include "item.h"
-#include "value.h"
+#include <language/item.h>
+#include <language/value.h>
#include <logging/translator.h>
#include <tools/error.h>
@@ -140,7 +140,7 @@ private:
value->setFile(conditionalValue->file());
item->setProperty(propertyName, value);
value->setSourceCode(StringConstants::baseVar());
- value->setSourceUsesBaseFlag();
+ value->setSourceUsesBase();
}
m_alternative.value = conditionalValue;
value->addAlternative(m_alternative);
diff --git a/src/lib/corelib/language/astpropertiesitemhandler.h b/src/lib/corelib/loader/astpropertiesitemhandler.h
index 413512ee5..413512ee5 100644
--- a/src/lib/corelib/language/astpropertiesitemhandler.h
+++ b/src/lib/corelib/loader/astpropertiesitemhandler.h
diff --git a/src/lib/corelib/loader/dependenciesresolver.cpp b/src/lib/corelib/loader/dependenciesresolver.cpp
new file mode 100644
index 000000000..06852afa9
--- /dev/null
+++ b/src/lib/corelib/loader/dependenciesresolver.cpp
@@ -0,0 +1,1144 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "dependenciesresolver.h"
+
+#include "itemreader.h"
+#include "loaderutils.h"
+#include "moduleinstantiator.h"
+#include "moduleloader.h"
+#include "moduleproviderloader.h"
+#include "productitemmultiplexer.h"
+
+#include <language/scriptengine.h>
+#include <language/evaluator.h>
+#include <language/item.h>
+#include <language/itempool.h>
+#include <language/value.h>
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/fileinfo.h>
+#include <tools/preferences.h>
+#include <tools/profiling.h>
+#include <tools/settings.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <optional>
+#include <queue>
+#include <unordered_map>
+
+namespace qbs::Internal {
+namespace {
+enum class HandleDependency { Use, Ignore, Defer };
+
+class LoadModuleResult
+{
+public:
+ Item *moduleItem = nullptr;
+ ProductContext *product = nullptr;
+ HandleDependency handleDependency = HandleDependency::Use;
+};
+
+// Corresponds completely to a Depends item.
+// May result in more than one module, due to "multiplexing" properties such as subModules etc.
+// May also result in no module at all, e.g. if productTypes does not match anything.
+class EvaluatedDependsItem
+{
+public:
+ Item *item = nullptr;
+ QualifiedId name;
+ QStringList subModules;
+ FileTags productTypes;
+ QStringList multiplexIds;
+ std::optional<QStringList> profiles;
+ VersionRange versionRange;
+ QVariantMap parameters;
+ bool limitToSubProject = false;
+ FallbackMode fallbackMode = FallbackMode::Enabled;
+ bool requiredLocally = true;
+ bool requiredGlobally = true;
+};
+
+// As opposed to EvaluatedDependsItem, one of these corresponds exactly to one module
+// to be loaded. Such an attempt might still fail, though, which may or may not result
+// in an error, depending on the value of Depends.required and other circumstances.
+class FullyResolvedDependsItem
+{
+public:
+ FullyResolvedDependsItem(ProductContext *product, const EvaluatedDependsItem &dependency);
+ FullyResolvedDependsItem(const EvaluatedDependsItem &dependency, QualifiedId name,
+ QString profile, QString multiplexId);
+ FullyResolvedDependsItem() = default;
+ static FullyResolvedDependsItem makeBaseDependency();
+
+ QString id() const;
+ CodeLocation location() const;
+ QString displayName() const;
+
+ // If product is non-null, we already know which product the dependency targets.
+ // This happens either if Depends.productTypes was set, or if we tried to load the
+ // dependency before and already identified the product, but could not complete the
+ // procedure because said product had itself not been handled yet.
+ ProductContext *product = nullptr;
+
+ Item *item = nullptr;
+ QualifiedId name;
+ QString profile;
+ QString multiplexId;
+ VersionRange versionRange;
+ QVariantMap parameters;
+ bool limitToSubProject = false;
+ FallbackMode fallbackMode = FallbackMode::Enabled;
+ bool requiredLocally = true;
+ bool requiredGlobally = true;
+ bool checkProduct = true;
+};
+
+class DependenciesResolvingState
+{
+public:
+ Item *loadingItem = nullptr;
+ FullyResolvedDependsItem loadingItemOrigin;
+ std::queue<Item *> pendingDependsItems;
+ std::optional<EvaluatedDependsItem> currentDependsItem;
+ std::queue<FullyResolvedDependsItem> pendingResolvedDependencies;
+ bool requiredByLoadingItem = true;
+};
+} // 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 DependenciesResolver::Private
+{
+public:
+ Private(LoaderState &loaderState) : loaderState(loaderState) {}
+
+ void initializeState();
+ 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);
+ ProductContext *findMatchingProduct(const FullyResolvedDependsItem &dependency);
+ Item *findMatchingModule(const FullyResolvedDependsItem &dependency);
+ std::pair<bool, HandleDependency> checkProductDependency(
+ const FullyResolvedDependsItem &depSpec, const ProductContext &dep);
+ void checkModule(const FullyResolvedDependsItem &dependency, Item *moduleItem,
+ ProductContext *productDep);
+ void checkForModuleNamePrefixCollision(const FullyResolvedDependsItem &dependency);
+ Item::Module createModule(const FullyResolvedDependsItem &dependency, Item *item,
+ ProductContext *productDep);
+ void adjustDependsItemForMultiplexing(Item *dependsItem);
+ std::optional<EvaluatedDependsItem> evaluateDependsItem(Item *item);
+ std::queue<FullyResolvedDependsItem> multiplexDependency(
+ const EvaluatedDependsItem &dependency);
+ 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;
+};
+
+DependenciesResolver::DependenciesResolver(LoaderState &loaderState)
+ : d(makePimpl<Private>(loaderState)) {}
+DependenciesResolver::~DependenciesResolver() = default;
+
+bool DependenciesResolver::resolveDependencies(ProductContext &product, Deferral deferral)
+{
+ QBS_CHECK(!product.dependenciesResolved);
+
+ AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
+ ? &d->elapsedTime : nullptr);
+
+ d->product = &product;
+ d->deferral = deferral;
+ d->stateStack = &d->statePerProduct[&product];
+
+ d->initializeState();
+ SearchPathsManager searchPathsMgr(d->loaderState.itemReader(), product.searchPaths);
+
+ while (!d->stateStack->empty()) {
+ auto &state = d->stateStack->front();
+
+ // If we have pending FullyResolvedDependsItems, then these are handled first.
+ if (d->handleResolvedDependencies() == HandleDependency::Defer)
+ return false;
+
+ // 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())
+ continue;
+
+ // If we have a pending EvaluatedDependsItem, we multiplex it and then handle
+ // the resulting FullyResolvedDependsItems, if there were any.
+ if (state.currentDependsItem) {
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+
+ // We postpone handling Depends.productTypes for as long as possible, because
+ // the full type of a product becomes available only after its modules have been loaded.
+ if (!state.currentDependsItem->productTypes.empty() && deferral == Deferral::Allowed)
+ return false;
+
+ state.pendingResolvedDependencies = d->multiplexDependency(*state.currentDependsItem);
+ state.currentDependsItem.reset();
+ continue;
+ }
+
+ // Here we have no resolved/evaluated Depends items of any kind, so we evaluate the next
+ // pending Depends item.
+ d->evaluateNextDependsItem();
+ if (state.currentDependsItem)
+ continue;
+
+ // No resolved or unresolved Depends items are left, so we're done with the current state.
+ QBS_CHECK(!state.currentDependsItem);
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+ QBS_CHECK(state.pendingDependsItems.empty());
+
+ // This ensures our invariant: A sorted module list in the product
+ // (dependers after dependencies).
+ if (d->stateStack->size() > 1) {
+ QBS_CHECK(state.loadingItem->type() == ItemType::ModuleInstance);
+ Item::Modules &modules = product.item->modules();
+ const auto loadingItemModule = std::find_if(modules.begin(), modules.end(),
+ [&](const Item::Module &m) {
+ return m.item == state.loadingItem;
+ });
+ QBS_CHECK(loadingItemModule != modules.end());
+ const Item::Module tempModule = *loadingItemModule;
+ modules.erase(loadingItemModule);
+ modules.push_back(tempModule);
+ }
+ 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->push_front(std::move(newState));
+ stateStack->front().pendingResolvedDependencies.push(
+ FullyResolvedDependsItem::makeBaseDependency());
+}
+
+void DependenciesResolver::Private::evaluateNextDependsItem()
+{
+ auto &state = stateStack->front();
+ while (!state.pendingDependsItems.empty()) {
+ QBS_CHECK(!state.currentDependsItem);
+ QBS_CHECK(state.pendingResolvedDependencies.empty());
+ Item * const dependsItem = state.pendingDependsItems.front();
+ state.pendingDependsItems.pop();
+ adjustDependsItemForMultiplexing(dependsItem);
+ if (auto evaluated = evaluateDependsItem(dependsItem)) {
+ evaluated->requiredGlobally = evaluated->requiredLocally
+ && state.loadingItemOrigin.requiredGlobally;
+ state.currentDependsItem = evaluated;
+ return;
+ }
+ }
+}
+
+HandleDependency DependenciesResolver::Private::handleResolvedDependencies()
+{
+ DependenciesResolvingState &state = stateStack->front();
+ while (!state.pendingResolvedDependencies.empty()) {
+ QBS_CHECK(!state.currentDependsItem);
+ const FullyResolvedDependsItem dependency = state.pendingResolvedDependencies.front();
+ try {
+ const LoadModuleResult res = loadModule(state.loadingItem, dependency);
+ switch (res.handleDependency) {
+ case HandleDependency::Defer:
+ QBS_CHECK(deferral == Deferral::Allowed);
+
+ // Optimization: We already looked up the product, so let's not do that again
+ // next time.
+ if (res.product)
+ state.pendingResolvedDependencies.front().product = res.product;
+
+ return HandleDependency::Defer;
+ case HandleDependency::Ignore:
+ // This happens if the dependency was not required or the module was already
+ // loaded via another path.
+ state.pendingResolvedDependencies.pop();
+ continue;
+ case HandleDependency::Use:
+ if (dependency.name.toString() == StringConstants::qbsModule()) {
+ // Shortcut: No need to look for recursive dependencies in the base module.
+ state.pendingResolvedDependencies.pop();
+ continue;
+ }
+ break;
+ }
+
+ QBS_CHECK(res.moduleItem);
+
+ // Now continue with the dependencies of the just-loaded module.
+ std::queue<Item *> moduleDependsItems;
+ for (Item * const child : res.moduleItem->children()) {
+ if (child->type() == ItemType::Depends)
+ moduleDependsItems.push(child);
+ }
+ state.pendingResolvedDependencies.pop();
+ stateStack->push_front(
+ {res.moduleItem, dependency, moduleDependsItems, {}, {},
+ dependency.requiredGlobally || state.requiredByLoadingItem});
+ stateStack->front().pendingResolvedDependencies.push(
+ FullyResolvedDependsItem::makeBaseDependency());
+ break;
+ } catch (const ErrorInfo &e) {
+ if (dependency.name.toString() == StringConstants::qbsModule())
+ throw e;
+
+ // 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();
+
+ // Unwind.
+ while (stateStack->size() > 1) {
+ const auto loadingItemModule = std::find_if(
+ modules.begin(), modules.end(), [&](const Item::Module &m) {
+ return m.item == stateStack->front().loadingItem;
+ });
+ for (auto it = loadingItemModule; it != modules.end(); ++it) {
+ createNonPresentModule(loaderState.itemPool(), it->name.toString(),
+ QLatin1String("error in Depends chain"), it->item);
+ }
+ modules.erase(loadingItemModule, modules.end());
+ stateStack->pop_front();
+ }
+
+ product->handleError(e);
+ return HandleDependency::Ignore;
+ }
+ }
+ return HandleDependency::Ignore;
+}
+
+// Produces an item of type ModuleInstance corresponding to the specified dependency.
+// The instance's prototype item is either of type Export (if the dependency is a product)
+// or of type Module (for an actual module).
+// The loadingItem parameter is either the depending product or another module. The newly
+// created module is added to the module list of the product item and additionally to the
+// loading item's one, if it is not the product. Its name is also injected into the respective
+// scopes.
+LoadModuleResult DependenciesResolver::Private::loadModule(
+ Item *loadingItem, const FullyResolvedDependsItem &dependency)
+{
+ qCDebug(lcModuleLoader) << "loadModule name:" << dependency.name.toString()
+ << "id:" << dependency.id();
+
+ QBS_CHECK(loadingItem);
+
+ ProductContext *productDep = nullptr;
+ Item *moduleItem = nullptr;
+
+ // The module might already have been loaded for this product (directly or indirectly).
+ const auto &[existingModule, moduleWithSameName]
+ = findExistingModule(dependency, product->item);
+ if (existingModule) {
+ // Merge version range and required property. These will be checked again
+ // after probes resolving.
+ if (existingModule->name.toString() != StringConstants::qbsModule())
+ updateModule(*existingModule, dependency);
+
+ QBS_CHECK(existingModule->item);
+ moduleItem = existingModule->item;
+ if (!contains(existingModule->loadingItems, loadingItem))
+ existingModule->loadingItems.push_back(loadingItem);
+ } else if (dependency.product) {
+ productDep = dependency.product; // We have already done the look-up.
+ } else if (!(productDep = findMatchingProduct(dependency))) {
+ moduleItem = findMatchingModule(dependency);
+ }
+
+ if (productDep) {
+ const auto checkResult = checkProductDependency(dependency, *productDep);
+
+ // We found a product dependency, but that product has not been handled yet,
+ // so stop dependency resolving for the current product and resume it later, when the
+ // dependency is ready.
+ if (checkResult.second == HandleDependency::Defer)
+ return {nullptr, productDep, HandleDependency::Defer};
+
+ if (checkResult.first) {
+ QBS_CHECK(productDep->mergedExportItem);
+ moduleItem = productDep->mergedExportItem->clone();
+ moduleItem->setParent(nullptr);
+
+ // Needed for isolated Export item evaluation.
+ moduleItem->setPrototype(productDep->mergedExportItem);
+ } else {
+ // The product dependency is faulty, but Depends.reqired was false.
+ productDep = nullptr;
+ }
+ }
+
+ if (moduleItem)
+ checkModule(dependency, moduleItem, productDep);
+
+ // Can only happen with multiplexing.
+ if (moduleWithSameName && moduleWithSameName != moduleItem)
+ QBS_CHECK(productDep);
+
+ // The loading name is only used to ensure consistent sorting in case of equal
+ // value priorities; see ModulePropertyMerger.
+ QString loadingName;
+ if (loadingItem == product->item) {
+ loadingName = product->name;
+ } else if (!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)});
+
+ // At this point, a null module item is only possible for a non-required dependency.
+ // Note that we still needed to to the instantiation above, as that injects the module
+ // name into the surrounding item for the ".present" check.
+ if (!moduleItem) {
+ QBS_CHECK(!dependency.requiredGlobally);
+ return {nullptr, nullptr, HandleDependency::Ignore};
+ }
+
+ const auto addLocalModule = [&] {
+ if (loadingItem->type() == ItemType::ModuleInstance
+ && !findExistingModule(dependency, loadingItem).first) {
+ loadingItem->addModule(createModule(dependency, moduleItem, productDep));
+ }
+ };
+
+ // The module has already been loaded, so we don't need to add it to the product's list of
+ // modules, nor do we need to handle its dependencies. The only thing we might need to
+ // do is to add it to the "local" list of modules of the loading item, if it isn't in there yet.
+ if (existingModule) {
+ addLocalModule();
+ return {nullptr, nullptr, HandleDependency::Ignore};
+ }
+
+ qCDebug(lcModuleLoader) << "module loaded:" << dependency.name.toString();
+ if (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);
+ addLocalModule();
+ }
+ return {moduleItem, nullptr, HandleDependency::Use};
+}
+
+std::pair<Item::Module *, Item *> DependenciesResolver::Private::findExistingModule(
+ const FullyResolvedDependsItem &dependency, Item *item)
+{
+ if (!item) // Happens if and only if called via loadBaseModule().
+ return {};
+ Item *moduleWithSameName = nullptr;
+ for (Item::Module &m : item->modules()) {
+ if (m.name != dependency.name)
+ continue;
+ if (!m.productInfo) {
+ QBS_CHECK(!dependency.product);
+ return {&m, m.item};
+ }
+ if ((dependency.profile.isEmpty() || (m.productInfo->profile == dependency.profile))
+ && m.productInfo->multiplexId == dependency.multiplexId) {
+ return {&m, m.item};
+ }
+
+ // This can happen if there are dependencies to several variants of a multiplexed
+ // product.
+ moduleWithSameName = m.item;
+ }
+ return {nullptr, moduleWithSameName};
+}
+
+void DependenciesResolver::Private::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);
+
+ module.versionRange.narrowDown(dependency.versionRange);
+ module.required |= dependency.requiredGlobally;
+ if (int(stateStack->size()) > module.maxDependsChainLength)
+ module.maxDependsChainLength = stateStack->size();
+}
+
+ProductContext *DependenciesResolver::Private::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;
+}
+
+Item *DependenciesResolver::Private::findMatchingModule(
+ const FullyResolvedDependsItem &dependency)
+{
+ // If we can tell that this is supposed to be a product dependency, we can skip
+ // the module look-up.
+ if (!dependency.profile.isEmpty() || !dependency.multiplexId.isEmpty()) {
+ if (dependency.requiredGlobally) {
+ if (!dependency.profile.isEmpty()) {
+ throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
+ "for the requested profile '%3'.")
+ .arg(product->displayName(), dependency.displayName(),
+ dependency.profile),
+ product->item->location());
+ }
+ throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
+ .arg(product->displayName(), dependency.displayName()),
+ 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) {
+ Item * const proto = moduleItem;
+ moduleItem = moduleItem->clone();
+ 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;
+}
+
+std::pair<bool, HandleDependency> DependenciesResolver::Private::checkProductDependency(
+ const FullyResolvedDependsItem &depSpec, const ProductContext &dep)
+{
+ // Optimization: If we already checked the product earlier and then deferred, we don't
+ // need to check it again.
+ if (!depSpec.checkProduct)
+ return {true, HandleDependency::Use};
+
+ if (&dep == 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)
+ 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());
+ e.append(Tr::tr("Second product is '%1'.")
+ .arg(dep.displayName()), dep.item->location());
+ e.append(Tr::tr("Requested here."), depSpec.location());
+ throw e;
+ }
+
+ // This covers both the case of user-disabled products and products with errors.
+ // The latter are force-disabled in ProductContext::handleError().
+ if (product->project->topLevelProject->disabledItems.contains(dep.item)) {
+ if (depSpec.requiredGlobally) {
+ ErrorInfo e;
+ e.append(Tr::tr("Product '%1' depends on '%2',")
+ .arg(product->displayName(), dep.displayName()),
+ product->item->location());
+ e.append(Tr::tr("but product '%1' is disabled.").arg(dep.displayName()),
+ dep.item->location());
+ throw e;
+ }
+ return {false, HandleDependency::Ignore};
+ }
+ return {true, HandleDependency::Use};
+}
+
+void DependenciesResolver::Private::checkModule(
+ const FullyResolvedDependsItem &dependency, Item *moduleItem, ProductContext *productDep)
+{
+ for (auto it = stateStack->begin(); it != stateStack->end(); ++it) {
+ Item *itemToCheck = moduleItem;
+ if (it->loadingItem != itemToCheck) {
+ if (!productDep)
+ continue;
+ itemToCheck = productDep->item;
+ }
+ if (it->loadingItem != itemToCheck)
+ continue;
+ ErrorInfo e;
+ e.append(Tr::tr("Cyclic dependencies detected:"));
+ while (true) {
+ e.append(it->loadingItemOrigin.name.toString(),
+ it->loadingItemOrigin.location());
+ if (it->loadingItem->type() == ItemType::ModuleInstance) {
+ createNonPresentModule(loaderState.itemPool(),
+ it->loadingItemOrigin.name.toString(),
+ QLatin1String("cyclic dependency"), it->loadingItem);
+ }
+ if (it == stateStack->begin())
+ break;
+ --it;
+ }
+ e.append(dependency.name.toString(), dependency.location());
+ throw e;
+ }
+ checkForModuleNamePrefixCollision(dependency);
+}
+
+void DependenciesResolver::Private::adjustDependsItemForMultiplexing(Item *dependsItem)
+{
+ Evaluator &evaluator = loaderState.evaluator();
+ const QString name = evaluator.stringValue(dependsItem, StringConstants::nameProperty());
+ const bool productIsMultiplexed = !product->multiplexConfigurationId.isEmpty();
+ if (name == product->name) {
+ QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator.
+ return;
+ }
+ 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
+ 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 true;
+ };
+
+ // These are the allowed cases:
+ // (1) Normal dependency with no multiplexing whatsoever.
+ // (2) Both product and dependency are multiplexed.
+ // (2a) The profiles property is not set, we want to depend on the best
+ // matching variant.
+ // (2b) The profiles property is set, we want to depend on all variants
+ // with a matching profile.
+ // (3) The product is not multiplexed, but the dependency is.
+ // (3a) The profiles property is not set, the dependency has an aggregator.
+ // We want to depend on the aggregator.
+ // (3b) The profiles property is not set, the dependency does not have an
+ // aggregator. We want to depend on all the multiplexed variants.
+ // (3c) The profiles property is set, we want to depend on all variants
+ // with a matching profile regardless of whether an aggregator exists or not.
+ // (4) The product is multiplexed, but the dependency is not. We don't have to adapt
+ // any Depends items.
+ // (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;
+
+ // (3a)
+ if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet)
+ return;
+
+ QStringList multiplexIds;
+ const ShadowProductInfo shadowProductInfo = getShadowProductInfo(*product);
+ const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name;
+ const auto productMultiplexConfig
+ = ProductItemMultiplexer::multiplexIdToVariantMap(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(
+ StringConstants::multiplexConfigurationIdProperty());
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ multiplexId);
+ return;
+
+ }
+ // Otherwise collect partial matches and decide later
+ const auto dependencyMultiplexConfig = ProductItemMultiplexer::multiplexIdToVariantMap(
+ dependency->multiplexConfigurationId);
+
+ if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig))
+ multiplexIds << dependency->multiplexConfigurationId;
+ } else {
+ // (2b), (3b) or (3c)
+ const bool profileMatch = !profilesPropertyIsSet || profiles.empty()
+ || profiles.contains(dependency->profileName);
+ if (profileMatch)
+ multiplexIds << depMultiplexId;
+ }
+ }
+ if (multiplexIds.empty()) {
+ const QString productName = ProductItemMultiplexer::fullProductDisplayName(
+ product->name, product->multiplexConfigurationId);
+ throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. "
+ "There are no eligible multiplex candidates.").arg(productName,
+ name),
+ dependsItem->location());
+ }
+
+ // In case of (2a), at most 1 match is allowed
+ if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) {
+ QStringList candidateNames;
+ for (const auto &id : std::as_const(multiplexIds))
+ candidateNames << ProductItemMultiplexer::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(", "))),
+ dependsItem->location());
+ }
+
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(),
+ VariantValue::create(multiplexIds));
+
+}
+
+std::optional<EvaluatedDependsItem> DependenciesResolver::Private::evaluateDependsItem(Item *item)
+{
+ Evaluator &evaluator = loaderState.evaluator();
+ if (!product->project->topLevelProject->checkItemCondition(item, evaluator)) {
+ qCDebug(lcModuleLoader) << "Depends item disabled, ignoring.";
+ return {};
+ }
+
+ const QString name = evaluator.stringValue(item, StringConstants::nameProperty());
+ if (name == StringConstants::qbsModule()) // Redundant
+ return {};
+
+ bool submodulesPropertySet;
+ const QStringList submodules = evaluator.stringListValue(
+ item, StringConstants::submodulesProperty(), &submodulesPropertySet);
+ if (submodules.empty() && submodulesPropertySet) {
+ qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list.";
+ return {};
+ }
+ if (Q_UNLIKELY(submodules.size() > 1 && !item->id().isEmpty())) {
+ QString msg = Tr::tr("A Depends item with more than one module cannot have an id.");
+ throw ErrorInfo(msg, item->location());
+ }
+ bool productTypesWasSet;
+ const QStringList productTypes = evaluator.stringListValue(
+ item, StringConstants::productTypesProperty(), &productTypesWasSet);
+ if (!name.isEmpty() && !productTypes.isEmpty()) {
+ throw ErrorInfo(Tr::tr("The name and productTypes are mutually exclusive "
+ "in a Depends item."), item->location());
+ }
+ if (productTypes.isEmpty() && productTypesWasSet) {
+ qCDebug(lcModuleLoader) << "Ignoring Depends item with empty productTypes list.";
+ return {};
+ }
+ if (name.isEmpty() && !productTypesWasSet) {
+ throw ErrorInfo(Tr::tr("Either name or productTypes must be set in a Depends item"),
+ item->location());
+ }
+
+ 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;
+ if (productTypes.isEmpty()) {
+ const QStringList profileList = evaluator.stringListValue(
+ item, StringConstants::profilesProperty(), &profilesPropertyWasSet);
+ if (profilesPropertyWasSet)
+ profiles.emplace(profileList);
+ required = evaluator.boolValue(item, StringConstants::requiredProperty());
+ }
+ const Version minVersion = Version::fromString(
+ evaluator.stringValue(item, StringConstants::versionAtLeastProperty()));
+ const Version maxVersion = Version::fromString(
+ evaluator.stringValue(item, StringConstants::versionBelowProperty()));
+ const bool limitToSubProject = evaluator.boolValue(
+ item, StringConstants::limitToSubProjectProperty());
+ const QStringList multiplexIds = evaluator.stringListValue(
+ item, StringConstants::multiplexConfigurationIdsProperty());
+ adjustParametersScopes(item, item);
+ moduleLoader.forwardParameterDeclarations(item, product->item->modules());
+ const QVariantMap parameters = extractParameters(item);
+
+ return EvaluatedDependsItem{
+ item, QualifiedId::fromString(name), submodules, FileTags::fromStringList(productTypes),
+ multiplexIds, profiles, {minVersion, maxVersion}, parameters, limitToSubProject,
+ fallbackMode, 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)
+{
+ 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);
+ }
+ }
+ }
+ if (matchingProducts.empty()) {
+ qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything."
+ << dependency.item->location();
+ return {};
+ }
+ for (ProductContext * const match : matchingProducts)
+ dependencies.emplace(match, dependency);
+ return dependencies;
+ }
+
+ const QStringList profiles = dependency.profiles && !dependency.profiles->isEmpty()
+ ? *dependency.profiles : QStringList(QString());
+ const QStringList multiplexIds = !dependency.multiplexIds.isEmpty()
+ ? dependency.multiplexIds : QStringList(QString());
+ const QStringList subModules = !dependency.subModules.isEmpty()
+ ? dependency.subModules : QStringList(QString());
+ for (const QString &profile : profiles) {
+ for (const QString &multiplexId : multiplexIds) {
+ for (const QString &subModule : subModules) {
+ QualifiedId name = dependency.name;
+ if (!subModule.isEmpty())
+ name << subModule.split(QLatin1Char('.'));
+ dependencies.emplace(dependency, name, profile, multiplexId);
+ }
+ }
+ }
+ return dependencies;
+}
+
+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 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);
+ } 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(
+ const FullyResolvedDependsItem &dependency)
+{
+ if (!product->item)
+ return;
+
+ for (const Item::Module &m : product->item->modules()) {
+ if (m.name.length() == dependency.name.length()
+ || m.name.front() != dependency.name.front()) {
+ continue;
+ }
+ QualifiedId shortName;
+ QualifiedId longName;
+ if (m.name < dependency.name) {
+ 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")
+ .arg(shortName.toString(), longName.toString()), dependency.location());
+ }
+}
+
+Item::Module DependenciesResolver::Private::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.name = dependency.name;
+ m.required = dependency.requiredLocally;
+ m.versionRange = dependency.versionRange;
+ return m;
+}
+
+FullyResolvedDependsItem::FullyResolvedDependsItem(
+ ProductContext *product, const EvaluatedDependsItem &dependency)
+ : product(product), item(dependency.item), name(product->name),
+ versionRange(dependency.versionRange), parameters(dependency.parameters),
+ fallbackMode(FallbackMode::Disabled), 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) {}
+
+QString FullyResolvedDependsItem::id() const
+{
+ if (!item) {
+ QBS_CHECK(name.toString() == StringConstants::qbsModule());
+ return {};
+ }
+ return item->id();
+}
+
+CodeLocation FullyResolvedDependsItem::location() const
+{
+ if (!item) {
+ QBS_CHECK(name.toString() == StringConstants::qbsModule());
+ return {};
+ }
+ return item->location();
+}
+
+QString FullyResolvedDependsItem::displayName() const
+{
+ return ProductItemMultiplexer::fullProductDisplayName(name.toString(), multiplexId);
+}
+
+bool haveSameSubProject(const ProductContext &p1, const ProductContext &p2)
+{
+ for (const Item *otherParent = p2.item->parent(); otherParent;
+ otherParent = otherParent->parent()) {
+ if (otherParent == p1.item->parent())
+ return true;
+ }
+ return false;
+}
+
+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;
+ handleJsProperties(ctx, v, [&](const JSAtom &prop, const JSPropertyDescriptor &desc) {
+ const JSValue u = desc.value;
+ if (JS_IsError(ctx, u))
+ throw ErrorInfo(getJsString(ctx, u));
+ const QString name = getJsString(ctx, prop);
+ result[name] = (JS_IsObject(u) && !JS_IsArray(ctx, u) && !JS_IsRegExp(ctx, u))
+ ? safeToVariant(ctx, u) : getJsVariant(ctx, u);
+ });
+ return result;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/dependenciesresolver.h b/src/lib/corelib/loader/dependenciesresolver.h
new file mode 100644
index 000000000..0b6d57023
--- /dev/null
+++ b/src/lib/corelib/loader/dependenciesresolver.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/pimpl.h>
+#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);
+
+private:
+ class Private;
+ Pimpl<Private> d;
+};
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/groupshandler.cpp b/src/lib/corelib/loader/groupshandler.cpp
new file mode 100644
index 000000000..a0cab5367
--- /dev/null
+++ b/src/lib/corelib/loader/groupshandler.cpp
@@ -0,0 +1,316 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "groupshandler.h"
+
+#include "loaderutils.h"
+#include "moduleinstantiator.h"
+
+#include <language/evaluator.h>
+#include <language/item.h>
+#include <language/value.h>
+#include <logging/translator.h>
+#include <tools/profiling.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+namespace qbs::Internal {
+class GroupsHandler::Private
+{
+public:
+ Private(LoaderState &loaderState) : loaderState(loaderState) {}
+
+ 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 propagateModulesFromParent(Item *group);
+ void handleGroup(Item *product, 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;
+};
+
+GroupsHandler::GroupsHandler(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) {}
+GroupsHandler::~GroupsHandler() = default;
+
+void GroupsHandler::setupGroups(Item *product, Item *productScope)
+{
+ 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);
+ }
+}
+
+std::unordered_map<const Item *, QualifiedIdSet> GroupsHandler::modulePropertiesSetInGroups() const
+{
+ return d->modulePropsSetInGroups;
+}
+
+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));
+}
+
+void GroupsHandler::Private::gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix,
+ QualifiedIdSet &properties)
+{
+ const Item::PropertyMap &props = iv->item()->properties();
+ for (auto it = props.cbegin(); it != props.cend(); ++it) {
+ switch (it.value()->type()) {
+ case Value::JSSourceValueType:
+ properties << (QualifiedId(prefix) << it.key());
+ break;
+ case Value::ItemValueType:
+ if (iv->item()->type() == ItemType::ModulePrefix) {
+ gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(),
+ QualifiedId(prefix) << it.key(), properties);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void GroupsHandler::Private::markModuleTargetGroups(Item *group, const Item::Module &module)
+{
+ QBS_CHECK(group->type() == ItemType::Group);
+ if (loaderState.evaluator().boolValue(group, StringConstants::filesAreTargetsProperty())) {
+ group->setProperty(StringConstants::modulePropertyInternal(),
+ VariantValue::create(module.name.toString()));
+ }
+ for (Item * const child : group->children())
+ markModuleTargetGroups(child, module);
+}
+
+void GroupsHandler::Private::moveGroupsFromModuleToProduct(Item *product, Item *productScope,
+ const Item::Module &module)
+{
+ if (!module.item->isPresentModule())
+ return;
+ for (auto it = module.item->children().begin(); it != module.item->children().end();) {
+ Item * const child = *it;
+ if (child->type() != ItemType::Group) {
+ ++it;
+ continue;
+ }
+ child->setScope(productScope);
+ setScopeForDescendants(child, productScope);
+ Item::addChild(product, child);
+ markModuleTargetGroups(child, module);
+ it = module.item->children().erase(it);
+ }
+}
+
+void GroupsHandler::Private::moveGroupsFromModulesToProduct(Item *product, Item *productScope)
+{
+ for (const Item::Module &module : product->modules())
+ moveGroupsFromModuleToProduct(product, productScope, 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)
+{
+ 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);
+ QBS_CHECK(targetItem->type() == ItemType::ModuleInstancePlaceholder);
+ targetItem->setPrototype(m.item);
+
+ Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope);
+ moduleScope->setFile(group->file());
+ moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids
+ moduleScope->setScope(group);
+ targetItem->setScope(moduleScope);
+
+ targetItem->setFile(m.item->file());
+
+ // "parent" should point to the group/artifact parent
+ targetItem->setParent(group->parent());
+
+ targetItem->setOuterItem(m.item);
+
+ m.item = targetItem;
+ group->addModule(m);
+ moduleInstancesForGroup.insert(m.name, targetItem);
+ }
+
+ // Step 2: Make the inter-module references point to the instances created in step 1.
+ for (const Item::Module &module : group->modules()) {
+ Item::Modules adaptedModules;
+ const Item::Modules &oldModules = module.item->prototype()->modules();
+ for (Item::Module depMod : oldModules) {
+ depMod.item = moduleInstancesForGroup.value(depMod.name);
+ adaptedModules << depMod;
+ if (depMod.name.front() == module.name.front())
+ continue;
+ const ItemValuePtr &modulePrefix = group->itemProperty(depMod.name.front());
+ QBS_CHECK(modulePrefix);
+ module.item->setProperty(depMod.name.front(), modulePrefix);
+ }
+ module.item->setModules(adaptedModules);
+ }
+
+ const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(group);
+ if (propsSetInGroup.empty())
+ return;
+ modulePropsSetInGroups.insert(std::make_pair(group, propsSetInGroup));
+
+ // Step 3: Adapt scopes in values. This is potentially necessary if module properties
+ // get assigned on the group level.
+ for (const Item::Module &module : group->modules())
+ adjustScopesInGroupModuleInstances(group, module);
+}
+
+void GroupsHandler::Private::handleGroup(Item *product, Item *group)
+{
+ propagateModulesFromParent(group);
+ if (!loaderState.evaluator().boolValue(group, StringConstants::conditionProperty()))
+ disabledGroups << group;
+ for (Item * const child : group->children()) {
+ if (child->type() == ItemType::Group)
+ handleGroup(product, child);
+ }
+}
+
+void GroupsHandler::Private::adjustScopesInGroupModuleInstances(Item *groupItem,
+ const Item::Module &module)
+{
+ if (!module.item->isPresentModule())
+ return;
+
+ Item *modulePrototype = module.item->rootPrototype();
+ QBS_CHECK(modulePrototype->type() == ItemType::Module
+ || modulePrototype->type() == ItemType::Export);
+
+ const Item::PropertyDeclarationMap &propDecls = modulePrototype->propertyDeclarations();
+ for (const auto &decl : propDecls) {
+ const QString &propName = decl.name();
+
+ // Nothing gets inherited for module properties assigned directly in the group.
+ // In particular, setting a list property overwrites the value from the product's
+ // (or parent group's) instance completely, rather than appending to it
+ // (concatenation happens via outer.concat()).
+ ValuePtr propValue = module.item->ownProperty(propName);
+ if (propValue) {
+ propValue->setScope(module.item->scope(), {});
+ continue;
+ }
+
+ // Find the nearest prototype instance that has the value assigned.
+ // The result is either an instance of a parent group (or the parent group's
+ // parent group and so on) or the instance of the product or the module prototype.
+ // In the latter case, we don't have to do anything.
+ const Item *instanceWithProperty = module.item;
+ do {
+ instanceWithProperty = instanceWithProperty->prototype();
+ QBS_CHECK(instanceWithProperty);
+ propValue = instanceWithProperty->ownProperty(propName);
+ } while (!propValue);
+ QBS_CHECK(propValue);
+
+ if (propValue->type() != Value::JSSourceValueType)
+ continue;
+
+ if (propValue->scope())
+ module.item->setProperty(propName, propValue->clone());
+ }
+
+ for (const ValuePtr &prop : module.item->properties()) {
+ if (prop->type() != Value::JSSourceValueType) {
+ QBS_CHECK(!prop->next());
+ continue;
+ }
+ for (ValuePtr v = prop; v; v = v->next()) {
+ if (!v->scope())
+ continue;
+ for (const Item::Module &module : groupItem->modules()) {
+ if (v->scope() == module.item->prototype()) {
+ v->setScope(module.item, {});
+ break;
+ }
+ }
+ }
+ }
+}
+
+QualifiedIdSet GroupsHandler::Private::gatherModulePropertiesSetInGroup(const Item *group)
+{
+ QualifiedIdSet propsSetInGroup;
+ const Item::PropertyMap &props = group->properties();
+ for (auto it = props.cbegin(); it != props.cend(); ++it) {
+ if (it.value()->type() == Value::ItemValueType) {
+ gatherAssignedProperties(std::static_pointer_cast<ItemValue>(it.value()).get(),
+ QualifiedId(it.key()), propsSetInGroup);
+ }
+ }
+ return propsSetInGroup;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/groupshandler.h b/src/lib/corelib/loader/groupshandler.h
new file mode 100644
index 000000000..ff6afdad9
--- /dev/null
+++ b/src/lib/corelib/loader/groupshandler.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <language/qualifiedid.h>
+#include <tools/set.h>
+#include <tools/pimpl.h>
+
+#include <unordered_map>
+#include <utility>
+
+namespace qbs::Internal {
+class Item;
+class LoaderState;
+
+// Sets up Group items for the actual resolving stage. Responsibilities:
+// - Moving Group items located in modules over to the product.
+// - Identifying groups declaring target artifacts and marking them accordingly.
+// - Setting up group-level module instances to ensure proper resolving of per-group module
+// properties.
+// - As a side effect of the above point, collecting all properties set on the Group level
+// to help the ProjectResolver decide which properties need to be re-evaluated at all,
+// which is an important optimization (see commit 9cd8653eef).
+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;
+};
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/itemreader.cpp b/src/lib/corelib/loader/itemreader.cpp
new file mode 100644
index 000000000..8d9b2ccd4
--- /dev/null
+++ b/src/lib/corelib/loader/itemreader.cpp
@@ -0,0 +1,270 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "itemreader.h"
+
+#include "itemreadervisitorstate.h"
+#include "loaderutils.h"
+
+#include <language/deprecationinfo.h>
+#include <language/evaluator.h>
+#include <language/filecontext.h>
+#include <language/item.h>
+#include <language/value.h>
+#include <logging/categories.h>
+#include <tools/fileinfo.h>
+#include <tools/profiling.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+#include <tools/stlutils.h>
+
+#include <QtCore/qfileinfo.h>
+
+#include <algorithm>
+
+namespace qbs {
+namespace Internal {
+
+static void makePathsCanonical(QStringList &paths)
+{
+ Internal::removeIf(paths, [](QString &p) {
+ p = QFileInfo(p).canonicalFilePath();
+ return p.isEmpty();
+ });
+}
+
+ItemReader::ItemReader(LoaderState &loaderState) : m_loaderState(loaderState) {}
+
+void ItemReader::init()
+{
+ m_visitorState = std::make_unique<ItemReaderVisitorState>(m_loaderState.logger());
+ m_visitorState->setDeprecationWarningMode(m_loaderState.parameters().deprecationWarningMode());
+ m_projectFilePath = m_loaderState.parameters().projectFilePath();
+ setSearchPaths(m_loaderState.parameters().searchPaths());
+ m_elapsedTime = m_loaderState.parameters().logElapsedTime() ? 0 : -1;
+}
+
+ItemReader::~ItemReader() = default;
+
+void ItemReader::setSearchPaths(const QStringList &searchPaths)
+{
+ qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths;
+ m_searchPaths = searchPaths;
+ makePathsCanonical(m_searchPaths);
+ m_allSearchPaths.clear();
+}
+
+void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths)
+{
+ m_extraSearchPaths.push_back(extraSearchPaths);
+ makePathsCanonical(m_extraSearchPaths.back());
+ m_allSearchPaths.clear();
+}
+
+void ItemReader::popExtraSearchPaths()
+{
+ m_extraSearchPaths.pop_back();
+ m_allSearchPaths.clear();
+}
+
+const std::vector<QStringList> &ItemReader::extraSearchPathsStack() const
+{
+ return m_extraSearchPaths;
+}
+
+void ItemReader::setExtraSearchPathsStack(const std::vector<QStringList> &s)
+{
+ m_extraSearchPaths = s;
+ m_allSearchPaths.clear();
+}
+
+void ItemReader::clearExtraSearchPathsStack()
+{
+ m_extraSearchPaths.clear();
+ m_allSearchPaths.clear();
+}
+
+const QStringList &ItemReader::allSearchPaths() const
+{
+ if (m_allSearchPaths.empty()) {
+ std::for_each(m_extraSearchPaths.crbegin(), m_extraSearchPaths.crend(),
+ [this] (const QStringList &paths) {
+ m_allSearchPaths += paths;
+ });
+ m_allSearchPaths += m_searchPaths;
+ m_allSearchPaths.removeDuplicates();
+ }
+ return m_allSearchPaths;
+}
+
+Item *ItemReader::readFile(const QString &filePath)
+{
+ AccumulatingTimer readFileTimer(m_elapsedTime != -1 ? &m_elapsedTime : nullptr);
+ return m_visitorState->readFile(filePath, allSearchPaths(), &m_loaderState.itemPool());
+}
+
+Item *ItemReader::readFile(const QString &filePath, const CodeLocation &referencingLocation)
+{
+ try {
+ return readFile(filePath);
+ } catch (const ErrorInfo &e) {
+ if (e.hasLocation())
+ throw;
+ throw ErrorInfo(e.toString(), referencingLocation);
+ }
+}
+
+Set<QString> ItemReader::filesRead() const
+{
+ return m_visitorState->filesRead();
+}
+
+void ItemReader::handlePropertyOptions(Item *optionsItem)
+{
+ Evaluator &evaluator = m_loaderState.evaluator();
+ const QString name = evaluator.stringValue(optionsItem, StringConstants::nameProperty());
+ if (name.isEmpty()) {
+ throw ErrorInfo(Tr::tr("PropertyOptions item needs a name property"),
+ optionsItem->location());
+ }
+ const QString description = evaluator.stringValue(
+ optionsItem, StringConstants::descriptionProperty());
+ const auto removalVersion = Version::fromString(
+ evaluator.stringValue(optionsItem, StringConstants::removalVersionProperty()));
+ const auto allowedValues = evaluator.stringListValue(
+ optionsItem, StringConstants::allowedValuesProperty());
+
+ PropertyDeclaration decl = optionsItem->parent()->propertyDeclaration(name);
+ if (!decl.isValid()) {
+ decl.setName(name);
+ decl.setType(PropertyDeclaration::Variant);
+ }
+ decl.setDescription(description);
+ if (removalVersion.isValid()) {
+ DeprecationInfo di(removalVersion, description);
+ decl.setDeprecationInfo(di);
+ }
+ decl.setAllowedValues(allowedValues);
+ const ValuePtr property = optionsItem->parent()->property(name);
+ if (!property && !decl.isExpired()) {
+ throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'")
+ .arg(name), optionsItem->location());
+ }
+ if (property && decl.isExpired()) {
+ ErrorInfo e(Tr::tr("Property '%1' was scheduled for removal in version %2, but "
+ "is still present.")
+ .arg(name, removalVersion.toString()),
+ property->location());
+ e.append(Tr::tr("Removal version for '%1' specified here.").arg(name),
+ optionsItem->location());
+ m_visitorState->logger().printWarning(e);
+ }
+ optionsItem->parent()->setPropertyDeclaration(name, decl);
+}
+
+void ItemReader::handleAllPropertyOptionsItems(Item *item)
+{
+ QList<Item *> childItems = item->children();
+ auto childIt = childItems.begin();
+ while (childIt != childItems.end()) {
+ Item * const child = *childIt;
+ if (child->type() == ItemType::PropertyOptions) {
+ handlePropertyOptions(child);
+ childIt = childItems.erase(childIt);
+ } else {
+ handleAllPropertyOptionsItems(child);
+ ++childIt;
+ }
+ }
+ item->setChildren(childItems);
+}
+
+Item *ItemReader::setupItemFromFile(const QString &filePath, const CodeLocation &referencingLocation)
+{
+ Item *item = readFile(filePath, referencingLocation);
+ handleAllPropertyOptionsItems(item);
+ return item;
+}
+
+Item *ItemReader::wrapInProjectIfNecessary(Item *item)
+{
+ if (item->type() == ItemType::Project)
+ return item;
+ Item *prj = Item::create(item->pool(), ItemType::Project);
+ Item::addChild(prj, item);
+ prj->setFile(item->file());
+ prj->setLocation(item->location());
+ prj->setupForBuiltinType(m_loaderState.parameters().deprecationWarningMode(),
+ m_visitorState->logger());
+ return prj;
+}
+
+QStringList ItemReader::readExtraSearchPaths(Item *item, bool *wasSet)
+{
+ QStringList result;
+ const QStringList paths = m_loaderState.evaluator().stringListValue(
+ item, StringConstants::qbsSearchPathsProperty(), wasSet);
+ const JSSourceValueConstPtr prop = item->sourceProperty(
+ StringConstants::qbsSearchPathsProperty());
+
+ // Value can come from within a project file or as an overridden value from the user
+ // (e.g command line).
+ const QString basePath = FileInfo::path(prop ? prop->file()->filePath()
+ : m_projectFilePath);
+ for (const QString &path : paths)
+ result += FileInfo::resolvePath(basePath, path);
+ return result;
+}
+
+SearchPathsManager::SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths)
+ : m_itemReader(itemReader),
+ m_oldSize(itemReader.extraSearchPathsStack().size())
+{
+ if (!extraSearchPaths.isEmpty())
+ m_itemReader.pushExtraSearchPaths(extraSearchPaths);
+}
+
+SearchPathsManager::~SearchPathsManager()
+{
+ while (m_itemReader.extraSearchPathsStack().size() > m_oldSize)
+ m_itemReader.popExtraSearchPaths();
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/loader/itemreader.h
index 6b2531cf2..01e9ae18d 100644
--- a/src/lib/corelib/language/itemreader.h
+++ b/src/lib/corelib/loader/itemreader.h
@@ -49,11 +49,13 @@
#include <memory>
namespace qbs {
+class SetupProjectParameters;
namespace Internal {
-
+class Evaluator;
class Item;
class ItemPool;
class ItemReaderVisitorState;
+class LoaderState;
/*
* Reads a qbs file and creates a tree of Item objects.
@@ -67,11 +69,10 @@ class ItemReaderVisitorState;
class ItemReader
{
public:
- ItemReader(Logger &logger);
+ ItemReader(LoaderState &loaderState);
~ItemReader();
+ void init();
- void setPool(ItemPool *pool) { m_pool = pool; }
- void setSearchPaths(const QStringList &searchPaths);
void pushExtraSearchPaths(const QStringList &extraSearchPaths);
void popExtraSearchPaths();
const std::vector<QStringList> &extraSearchPathsStack() const;
@@ -79,25 +80,43 @@ public:
void clearExtraSearchPathsStack();
const QStringList &allSearchPaths() const;
- Item *readFile(const QString &filePath);
- Item *readFile(const QString &filePath, const CodeLocation &referencingLocation);
+ // Parses a file, creates an item for it, generates PropertyDeclarations from
+ // PropertyOptions items and removes said items from the item tree.
+ Item *setupItemFromFile(const QString &filePath, const CodeLocation &referencingLocation);
+
+ Item *wrapInProjectIfNecessary(Item *item);
+ QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr);
Set<QString> filesRead() const;
- void setEnableTiming(bool on);
qint64 elapsedTime() const { return m_elapsedTime; }
- void setDeprecationWarningMode(DeprecationWarningMode mode);
-
private:
- ItemPool *m_pool = nullptr;
+ void setSearchPaths(const QStringList &searchPaths);
+ Item *readFile(const QString &filePath);
+ Item *readFile(const QString &filePath, const CodeLocation &referencingLocation);
+ void handlePropertyOptions(Item *optionsItem);
+ void handleAllPropertyOptionsItems(Item *item);
+
+ LoaderState &m_loaderState;
QStringList m_searchPaths;
std::vector<QStringList> m_extraSearchPaths;
mutable QStringList m_allSearchPaths;
- const std::unique_ptr<ItemReaderVisitorState> m_visitorState;
+ std::unique_ptr<ItemReaderVisitorState> m_visitorState;
+ QString m_projectFilePath;
qint64 m_elapsedTime = -1;
};
+class SearchPathsManager {
+public:
+ SearchPathsManager(ItemReader &itemReader, const QStringList &extraSearchPaths = {});
+ ~SearchPathsManager();
+
+private:
+ ItemReader &m_itemReader;
+ size_t m_oldSize{0};
+};
+
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/language/itemreaderastvisitor.cpp b/src/lib/corelib/loader/itemreaderastvisitor.cpp
index 721f24079..48bd0af1b 100644
--- a/src/lib/corelib/language/itemreaderastvisitor.cpp
+++ b/src/lib/corelib/loader/itemreaderastvisitor.cpp
@@ -41,16 +41,16 @@
#include "astimportshandler.h"
#include "astpropertiesitemhandler.h"
-#include "asttools.h"
-#include "builtindeclarations.h"
-#include "filecontext.h"
-#include "identifiersearch.h"
-#include "item.h"
#include "itemreadervisitorstate.h"
-#include "value.h"
#include <api/languageinfo.h>
#include <jsextensions/jsextensions.h>
+#include <language/asttools.h>
+#include <language/builtindeclarations.h>
+#include <language/filecontext.h>
+#include <language/identifiersearch.h>
+#include <language/item.h>
+#include <language/value.h>
#include <parser/qmljsast_p.h>
#include <tools/codelocation.h>
#include <tools/error.h>
@@ -268,7 +268,7 @@ bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement,
QBS_CHECK(value);
if (AST::cast<AST::Block *>(statement))
- value->m_flags |= JSSourceValue::HasFunctionForm;
+ value->setHasFunctionForm();
value->setFile(m_file);
value->setSourceCode(textViewOf(m_file->content(), statement));
@@ -282,11 +282,11 @@ bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement,
idsearch.add(StringConstants::originalVar(), &usesOriginal);
idsearch.start(statement);
if (usesBase)
- value->m_flags |= JSSourceValue::SourceUsesBase;
+ value->setSourceUsesBase();
if (usesOuter)
- value->m_flags |= JSSourceValue::SourceUsesOuter;
+ value->setSourceUsesOuter();
if (usesOriginal)
- value->m_flags |= JSSourceValue::SourceUsesOriginal;
+ value->setSourceUsesOriginal();
return false;
}
@@ -321,7 +321,7 @@ Item *ItemReaderASTVisitor::targetItemForBinding(const QStringList &bindingName,
void ItemReaderASTVisitor::inheritItem(Item *dst, const Item *src)
{
int insertPos = 0;
- for (Item *child : qAsConst(src->m_children)) {
+ for (Item *child : std::as_const(src->m_children)) {
dst->m_children.insert(insertPos++, child);
child->m_parent = dst;
}
diff --git a/src/lib/corelib/language/itemreaderastvisitor.h b/src/lib/corelib/loader/itemreaderastvisitor.h
index 963b78471..a102b2821 100644
--- a/src/lib/corelib/language/itemreaderastvisitor.h
+++ b/src/lib/corelib/loader/itemreaderastvisitor.h
@@ -40,8 +40,8 @@
#ifndef QBS_ITEMREADERASTVISITOR_H
#define QBS_ITEMREADERASTVISITOR_H
-#include "forward_decls.h"
-#include "itemtype.h"
+#include <language/forward_decls.h>
+#include <language/itemtype.h>
#include <logging/logger.h>
#include <parser/qmljsastvisitor_p.h>
@@ -88,7 +88,7 @@ private:
Logger &m_logger;
QHash<QStringList, QString> m_typeNameToFile;
Item *m_item = nullptr;
- ItemType m_instanceItemType = ItemType::ModuleInstance;
+ ItemType m_instanceItemType = ItemType::ModuleInstancePlaceholder;
};
} // namespace Internal
diff --git a/src/lib/corelib/language/itemreadervisitorstate.cpp b/src/lib/corelib/loader/itemreadervisitorstate.cpp
index a51b7eab4..57484043a 100644
--- a/src/lib/corelib/language/itemreadervisitorstate.cpp
+++ b/src/lib/corelib/loader/itemreadervisitorstate.cpp
@@ -38,10 +38,10 @@
****************************************************************************/
#include "itemreadervisitorstate.h"
-#include "asttools.h"
-#include "filecontext.h"
#include "itemreaderastvisitor.h"
+#include <language/asttools.h>
+#include <language/filecontext.h>
#include <logging/translator.h>
#include <parser/qmljsengine_p.h>
#include <parser/qmljslexer_p.h>
diff --git a/src/lib/corelib/language/itemreadervisitorstate.h b/src/lib/corelib/loader/itemreadervisitorstate.h
index 90f88cd5e..dc22cfb42 100644
--- a/src/lib/corelib/language/itemreadervisitorstate.h
+++ b/src/lib/corelib/loader/itemreadervisitorstate.h
@@ -39,7 +39,6 @@
#ifndef QBS_ITEMREADERVISITORSTATE_H
#define QBS_ITEMREADERVISITORSTATE_H
-#include <logging/logger.h>
#include <tools/deprecationwarningmode.h>
#include <tools/set.h>
@@ -51,6 +50,7 @@ namespace qbs {
namespace Internal {
class Item;
class ItemPool;
+class Logger;
class ItemReaderVisitorState
{
@@ -58,6 +58,7 @@ public:
ItemReaderVisitorState(Logger &logger);
~ItemReaderVisitorState();
+ Logger &logger() { return m_logger; }
Set<QString> filesRead() const { return m_filesRead; }
Item *readFile(const QString &filePath, const QStringList &searchPaths, ItemPool *itemPool);
diff --git a/src/lib/corelib/loader/loaderutils.cpp b/src/lib/corelib/loader/loaderutils.cpp
new file mode 100644
index 000000000..98ac10a92
--- /dev/null
+++ b/src/lib/corelib/loader/loaderutils.cpp
@@ -0,0 +1,196 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "loaderutils.h"
+
+#include "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/language.h>
+#include <language/value.h>
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/progressobserver.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+namespace qbs::Internal {
+
+void mergeParameters(QVariantMap &dst, const QVariantMap &src)
+{
+ 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());
+}
+
+void adjustParametersScopes(Item *item, Item *scope)
+{
+ if (item->type() == ItemType::ModuleParameters) {
+ item->setScope(scope);
+ return;
+ }
+
+ for (const auto &value : item->properties()) {
+ if (value->type() == Value::ItemValueType)
+ adjustParametersScopes(std::static_pointer_cast<ItemValue>(value)->item(), scope);
+ }
+}
+
+QString ProductContext::uniqueName() const
+{
+ return ResolvedProduct::uniqueName(name, multiplexConfigurationId);
+}
+
+QString ProductContext::displayName() const
+{
+ return ProductItemMultiplexer::fullProductDisplayName(name, multiplexConfigurationId);
+}
+
+void ProductContext::handleError(const ErrorInfo &error)
+{
+ const bool alreadyHadError = info.delayedError.hasError();
+ if (!alreadyHadError) {
+ info.delayedError.append(Tr::tr("Error while handling product '%1':")
+ .arg(name), item->location());
+ }
+ if (error.isInternalError()) {
+ if (alreadyHadError) {
+ qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString()
+ << "in product" << name;
+ return;
+ }
+ }
+ const auto errorItems = error.items();
+ for (const ErrorItem &ei : errorItems)
+ info.delayedError.append(ei.description(), ei.codeLocation());
+ project->topLevelProject->productInfos[item] = info;
+ project->topLevelProject->disabledItems << item;
+ project->topLevelProject->erroneousProducts.insert(name);
+}
+
+bool TopLevelProjectContext::checkItemCondition(Item *item, Evaluator &evaluator)
+{
+ if (evaluator.boolValue(item, StringConstants::conditionProperty()))
+ return true;
+ disabledItems += item;
+ return false;
+}
+
+void TopLevelProjectContext::checkCancelation(const SetupProjectParameters &parameters)
+{
+ if (progressObserver && progressObserver->canceled()) {
+ throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.")
+ .arg(TopLevelProject::deriveId(
+ parameters.finalBuildConfigurationTree())));
+ }
+}
+
+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);
+ })
+ {}
+
+ const SetupProjectParameters &parameters;
+ ItemPool &itemPool;
+ Evaluator &evaluator;
+ Logger &logger;
+
+ TopLevelProjectContext topLevelProject;
+ ItemReader itemReader;
+ ProbesResolver probesResolver;
+ ModulePropertyMerger propertyMerger;
+ LocalProfiles localProfiles;
+ ModuleInstantiator moduleInstantiator;
+ DependenciesResolver dependenciesResolver;
+ ProductItemMultiplexer multiplexer;
+};
+
+LoaderState::LoaderState(const SetupProjectParameters &parameters, ItemPool &itemPool,
+ Evaluator &evaluator, Logger &logger)
+ : d(makePimpl<Private>(*this, parameters, itemPool, evaluator, 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; }
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/loaderutils.h b/src/lib/corelib/loader/loaderutils.h
new file mode 100644
index 000000000..db05698a5
--- /dev/null
+++ b/src/lib/corelib/loader/loaderutils.h
@@ -0,0 +1,179 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <language/filetags.h>
+#include <language/forward_decls.h>
+#include <language/qualifiedid.h>
+#include <tools/pimpl.h>
+#include <tools/set.h>
+#include <tools/version.h>
+
+#include <QStringList>
+#include <QVariant>
+
+#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;
+
+using ModulePropertiesPerGroup = std::unordered_map<const Item *, QualifiedIdSet>;
+using ShadowProductInfo = std::pair<bool, QString>;
+
+enum class FallbackMode { Enabled, Disabled };
+enum class Deferral { Allowed, NotAllowed };
+
+class ProductInfo
+{
+public:
+ std::vector<ProbeConstPtr> probes;
+ ModulePropertiesPerGroup modulePropertiesSetInGroups;
+ ErrorInfo delayedError;
+};
+
+class ProductContext
+{
+public:
+ QString uniqueName() const;
+ QString displayName() const;
+ void handleError(const ErrorInfo &error);
+
+ QString name;
+ Item *item = nullptr;
+ Item *scope = nullptr;
+ ProjectContext *project = nullptr;
+ Item *mergedExportItem = nullptr;
+ ProductInfo info;
+ QString profileName;
+ QString multiplexConfigurationId;
+ QVariantMap profileModuleProperties; // Tree-ified module properties from profile.
+ QVariantMap moduleProperties; // Tree-ified module properties from profile + overridden values.
+ QVariantMap defaultParameters; // In Export item.
+ QStringList searchPaths;
+
+ bool dependenciesResolved = false;
+};
+
+class TopLevelProjectContext
+{
+public:
+ TopLevelProjectContext() = default;
+ TopLevelProjectContext(const TopLevelProjectContext &) = delete;
+ TopLevelProjectContext &operator=(const TopLevelProjectContext &) = delete;
+ ~TopLevelProjectContext() { qDeleteAll(projects); }
+
+ 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;
+
+ // 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;
+};
+
+class ProjectContext
+{
+public:
+ QString name;
+ Item *item = nullptr;
+ Item *scope = nullptr;
+ TopLevelProjectContext *topLevelProject = nullptr;
+ std::vector<ProductContext> products;
+ std::vector<QStringList> searchPathsStack;
+};
+
+class LoaderState
+{
+public:
+ LoaderState(const SetupProjectParameters &parameters, ItemPool &itemPool, Evaluator &evaluator,
+ 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:
+ class Private;
+ Pimpl<Private> d;
+};
+
+void mergeParameters(QVariantMap &dst, const QVariantMap &src);
+ShadowProductInfo getShadowProductInfo(const ProductContext &product);
+void adjustParametersScopes(Item *item, Item *scope);
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/loader/localprofiles.cpp b/src/lib/corelib/loader/localprofiles.cpp
new file mode 100644
index 000000000..b59204492
--- /dev/null
+++ b/src/lib/corelib/loader/localprofiles.cpp
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "localprofiles.h"
+
+#include "loaderutils.h"
+
+#include <language/evaluator.h>
+#include <language/item.h>
+#include <language/qualifiedid.h>
+#include <language/scriptengine.h>
+#include <language/value.h>
+#include <logging/translator.h>
+#include <tools/profile.h>
+#include <tools/scripttools.h>
+#include <tools/stringconstants.h>
+
+namespace qbs::Internal {
+class LocalProfiles::Private
+{
+public:
+ Private(LoaderState &loaderState) : loaderState(loaderState) {}
+
+ 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;
+};
+
+LocalProfiles::LocalProfiles(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) {}
+LocalProfiles::~LocalProfiles() = default;
+
+void LocalProfiles::collectProfilesFromItems(Item *productOrProject, Item *projectScope)
+{
+ d->collectProfiles(productOrProject, projectScope);
+}
+
+const QVariantMap &LocalProfiles::profiles() const
+{
+ return d->profiles;
+}
+
+void LocalProfiles::Private::handleProfile(Item *profileItem)
+{
+ QVariantMap values;
+ evaluateProfileValues(QualifiedId(), profileItem, profileItem, values);
+ const bool condition = values.take(StringConstants::conditionProperty()).toBool();
+ if (!condition)
+ return;
+ const QString profileName = values.take(StringConstants::nameProperty()).toString();
+ if (profileName.isEmpty())
+ throw ErrorInfo(Tr::tr("Every Profile item must have a name"), profileItem->location());
+ if (profileName == Profile::fallbackName()) {
+ throw ErrorInfo(Tr::tr("Reserved name '%1' cannot be used for an actual profile.")
+ .arg(profileName), profileItem->location());
+ }
+ if (profiles.contains(profileName)) {
+ throw ErrorInfo(Tr::tr("Local profile '%1' redefined.").arg(profileName),
+ profileItem->location());
+ }
+ profiles.insert(profileName, values);
+}
+
+void LocalProfiles::Private::evaluateProfileValues(const QualifiedId &namePrefix, Item *item,
+ Item *profileItem, QVariantMap &values)
+{
+ const Item::PropertyMap &props = item->properties();
+ for (auto it = props.begin(); it != props.end(); ++it) {
+ QualifiedId name = namePrefix;
+ name << it.key();
+ switch (it.value()->type()) {
+ case Value::ItemValueType:
+ evaluateProfileValues(name, std::static_pointer_cast<ItemValue>(it.value())->item(),
+ profileItem, values);
+ break;
+ case Value::VariantValueType:
+ values.insert(name.join(QLatin1Char('.')),
+ std::static_pointer_cast<VariantValue>(it.value())->value());
+ break;
+ case Value::JSSourceValueType:
+ if (item != profileItem)
+ item->setScope(profileItem);
+ const ScopedJsValue sv(loaderState.evaluator().engine()->context(),
+ loaderState.evaluator().value(item, it.key()));
+ values.insert(name.join(QLatin1Char('.')),
+ getJsVariant(loaderState.evaluator().engine()->context(), sv));
+ break;
+ }
+ }
+}
+
+void LocalProfiles::Private::collectProfiles(Item *productOrProject, Item *projectScope)
+{
+ Item * scope = productOrProject->type() == ItemType::Project ? projectScope : nullptr;
+ for (auto it = productOrProject->children().begin();
+ it != productOrProject->children().end();) {
+ Item * const childItem = *it;
+ if (childItem->type() == ItemType::Profile) {
+ if (!scope) {
+ const ItemValuePtr itemValue = ItemValue::create(productOrProject);
+ scope = Item::create(productOrProject->pool(), ItemType::Scope);
+ scope->setProperty(StringConstants::productVar(), itemValue);
+ scope->setFile(productOrProject->file());
+ scope->setScope(projectScope);
+ }
+ childItem->setScope(scope);
+ try {
+ handleProfile(childItem);
+ } catch (const ErrorInfo &e) {
+ handlePropertyError(e, loaderState.parameters(), loaderState.logger());
+ }
+ it = productOrProject->children().erase(it); // TODO: delete item and scope
+ } else {
+ if (childItem->type() == ItemType::Product)
+ collectProfiles(childItem, projectScope);
+ ++it;
+ }
+ }
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/localprofiles.h b/src/lib/corelib/loader/localprofiles.h
new file mode 100644
index 000000000..e31c8c81b
--- /dev/null
+++ b/src/lib/corelib/loader/localprofiles.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/pimpl.h>
+#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;
+};
+
+} // namespace qbs::Internal
+
diff --git a/src/lib/corelib/loader/moduleinstantiator.cpp b/src/lib/corelib/loader/moduleinstantiator.cpp
new file mode 100644
index 000000000..73b676be4
--- /dev/null
+++ b/src/lib/corelib/loader/moduleinstantiator.cpp
@@ -0,0 +1,339 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "moduleinstantiator.h"
+
+#include "loaderutils.h"
+#include "modulepropertymerger.h"
+
+#include <language/item.h>
+#include <language/itempool.h>
+#include <language/qualifiedid.h>
+#include <language/value.h>
+#include <logging/logger.h>
+#include <logging/translator.h>
+#include <tools/profiling.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <utility>
+
+namespace qbs::Internal {
+class ModuleInstantiator::Private
+{
+public:
+ Private(LoaderState &loaderState) : loaderState(loaderState) {}
+
+ 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);
+
+ LoaderState &loaderState;
+ qint64 elapsedTime = 0;
+};
+
+ModuleInstantiator::ModuleInstantiator(LoaderState &loaderState)
+ : d(makePimpl<Private>(loaderState)) {}
+ModuleInstantiator::~ModuleInstantiator() = default;
+
+void ModuleInstantiator::instantiate(const Context &context)
+{
+ AccumulatingTimer timer(d->loaderState.parameters().logElapsedTime()
+ ? &d->elapsedTime : 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);
+ }
+
+ // This strange-looking code deals with the fact that our syntax cannot properly handle
+ // dependencies on several multiplexed variants of the same product.
+ // See getOrSetModuleInstanceItem() below for details.
+ Item * const moduleItemForItemValues
+ = context.moduleWithSameName ? context.moduleWithSameName
+ : context.module;
+
+ // Now attach the module instance as an item value to the loading item, potentially
+ // evicting a previously attached placeholder item and merging its values into the instance.
+ // Note that we potentially do this twice, once for the actual loading item and once
+ // for the product item, if the two are different. The reason is this:
+ // For convenience, in the product item we allow attaching to properties from indirectly
+ // loaded modules. For instance:
+ // Product {
+ // Depends { name: "Qt.core" }
+ // cpp.cxxLanguageVersion: "c++20" // Works even though there is no Depends item for cpp
+ // }
+ // It's debatable whether that's a good feature, but it has been working (accidentally?)
+ // for a long time, and removing it now would break a lot of existing projects.
+ 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);
+ }
+}
+
+void ModuleInstantiator::Private::exchangePlaceholderItem(
+ Item *product, Item *loadingItem, const QString &loadingName, Item *moduleItemForItemValues,
+ const QualifiedId &moduleName, const QString &id, bool isProductModule, bool alreadyLoaded)
+{
+ // 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);
+
+ // 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(),
+ QLatin1String("not found"), newItem);
+ return;
+ }
+
+ // If the old and the new items are the same, it means the existing item value already
+ // pointed to a module instance (rather than a placeholder).
+ // This can happen in two cases:
+ // a) Multiple identical Depends items on the same level (easily possible with inheritance).
+ // b) Dependencies to multiplexed variants of the same product
+ // (see getOrSetModuleInstanceItem() below for details).
+ if (oldItem == newItem) {
+ QBS_CHECK(oldItem->type() == ItemType::ModuleInstance);
+ QBS_CHECK(alreadyLoaded || isProductModule);
+ return;
+ }
+
+ // In all other cases, our request to set the module instance item must have been honored.
+ QBS_CHECK(newItem == moduleItemForItemValues);
+
+ // If there was no placeholder item, we don't have to merge any values and are done.
+ if (!oldItem)
+ return;
+
+ // If an item was replaced, then it must have been a placeholder.
+ QBS_CHECK(oldItem->type() == ItemType::ModuleInstancePlaceholder);
+
+ // Prevent setting read-only properties.
+ for (auto it = oldItem->properties().cbegin(); it != oldItem->properties().cend(); ++it) {
+ const PropertyDeclaration &pd = moduleItemForItemValues->propertyDeclaration(it.key());
+ if (pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag)) {
+ throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()),
+ it.value()->location());
+ }
+ }
+
+ // Now merge the locally attached values into the actual module instance.
+ loaderState.propertyMerger().mergeFromLocalInstance(product, loadingItem, loadingName,
+ oldItem, moduleItemForItemValues);
+
+ // 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)
+{
+ return d->getOrSetModuleInstanceItem(containerItem, nullptr, name, {}, false).second;
+}
+
+Item *ModuleInstantiator::retrieveQbsItem(Item *containerItem)
+{
+ return retrieveModuleInstanceItem(containerItem, StringConstants::qbsModule());
+}
+
+// This important function deals with retrieving and setting (pseudo-)module instances from/on
+// items.
+// Use case 1: Suppose we resolve the dependency cpp in module Qt.core, which also contains
+// property bindings for cpp such as "cpp.defines: [...]".
+// The "cpp" part of this binding is represented by an ItemValue whose
+// item is of type ModuleInstancePlaceholder, originally set up by ItemReaderASTVisitor.
+// This function will be called with the actual cpp module item and will
+// replace the placeholder item in the item value. It will also return
+// the placeholder item for subsequent merging of its properties with the
+// ones of the actual module (in ModulePropertyMerger::mergeFromLocalInstance()).
+// If there were no cpp property bindings defined in Qt.core, then we'd still
+// have to replace the placeholder item, because references to "cpp" on the
+// right-hand-side of other properties must refer to the module item.
+// This is the common use of this function as employed by resolveProdsucDependencies().
+// Note that if a product has dependencies on more than one variant of a multiplexed
+// product, these dependencies are competing for the item value property name,
+// i.e. this case is not properly supported by the syntax. You must therefore not
+// export properties from multiplexed products that will be different between the
+// variants. In this function, the condition manifests itself by a module instance
+// being encountered instead of a module instance placeholder, in which case
+// nothing is done at all.
+// Use case 2: We inject a fake qbs module into a project item, so qbs properties set in profiles
+// can be accessed in the project level. Doing this is discouraged, and the
+// functionality is kept mostly for backwards compatibility. The moduleItem
+// parameter is null in this case, and the item will be created by the function itself.
+// Use case 3: A temporary qbs module is attached to a product during low-level setup, namely
+// in product multiplexing and setting qbs.profile.
+// Use case 4: Module propagation to the the Group level.
+// In all cases, the first returned item is the existing one, and the second returned item
+// is the new one. Depending on the use case, they might be null and might also be the same item.
+std::pair<const Item *, Item *> ModuleInstantiator::Private::getOrSetModuleInstanceItem(
+ Item *container, Item *moduleItem, const QualifiedId &moduleName, const QString &id,
+ bool replace)
+{
+ Item *instance = container;
+ const QualifiedId itemValueName
+ = !id.isEmpty() ? QualifiedId::fromString(id) : moduleName;
+ for (int i = 0; i < itemValueName.size(); ++i) {
+ const QString &moduleNameSegment = itemValueName.at(i);
+ const ValuePtr v = instance->ownProperty(itemValueName.at(i));
+ if (v && v->type() == Value::ItemValueType) {
+ ItemValue * const itemValue = std::static_pointer_cast<ItemValue>(v).get();
+ instance = itemValue->item();
+ if (i == itemValueName.size() - 1) {
+ if (replace && instance != moduleItem
+ && instance->type() == ItemType::ModuleInstancePlaceholder) {
+ if (!moduleItem) {
+ moduleItem = Item::create(&loaderState.itemPool(),
+ ItemType::ModuleInstancePlaceholder);
+ }
+ itemValue->setItem(moduleItem);
+ }
+ return {instance, itemValue->item()};
+ }
+ } else {
+ Item *newItem = i < itemValueName.size() - 1
+ ? Item::create(&loaderState.itemPool(), ItemType::ModulePrefix)
+ : moduleItem
+ ? moduleItem
+ : Item::create(&loaderState.itemPool(),
+ ItemType::ModuleInstancePlaceholder);
+ instance->setProperty(moduleNameSegment, ItemValue::create(newItem));
+ instance = newItem;
+ }
+ }
+ return {nullptr, instance};
+}
+
+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
new file mode 100644
index 000000000..7be886869
--- /dev/null
+++ b/src/lib/corelib/loader/moduleinstantiator.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/pimpl.h>
+#include <QtGlobal>
+
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
+namespace qbs::Internal {
+class Item;
+class LoaderState;
+class QualifiedId;
+
+// This class 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();
+
+ 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;
+};
+
+} // namespace qbs::Internal
+
diff --git a/src/lib/corelib/loader/moduleloader.cpp b/src/lib/corelib/loader/moduleloader.cpp
new file mode 100644
index 000000000..98548278f
--- /dev/null
+++ b/src/lib/corelib/loader/moduleloader.cpp
@@ -0,0 +1,536 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "moduleloader.h"
+
+#include "itemreader.h"
+#include "loaderutils.h"
+#include "moduleproviderloader.h"
+#include "productitemmultiplexer.h"
+
+#include <api/languageinfo.h>
+#include <language/evaluator.h>
+#include <language/value.h>
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/error.h>
+#include <tools/fileinfo.h>
+#include <tools/hostosinfo.h>
+#include <tools/profiling.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <QDirIterator>
+#include <QHash>
+
+#include <unordered_map>
+#include <utility>
+
+namespace qbs::Internal {
+
+class ModuleLoader::Private
+{
+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::ModuleLoader(LoaderState &loaderState) : d(makePimpl<Private>(loaderState)) { }
+
+ModuleLoader::~ModuleLoader() = default;
+
+struct PrioritizedItem
+{
+ PrioritizedItem(Item *item, int priority, int searchPathIndex)
+ : item(item), priority(priority), searchPathIndex(searchPathIndex) { }
+
+ Item * const item;
+ int priority = 0;
+ const int searchPathIndex;
+};
+
+static Item *chooseModuleCandidate(const std::vector<PrioritizedItem> &candidates,
+ const QString &moduleName)
+{
+ // TODO: This should also consider the version requirement.
+
+ auto maxIt = std::max_element(
+ candidates.begin(), candidates.end(),
+ [] (const PrioritizedItem &a, const PrioritizedItem &b) {
+ if (a.priority < b.priority)
+ return true;
+ if (a.priority > b.priority)
+ return false;
+ return a.searchPathIndex > b.searchPathIndex;
+ });
+
+ size_t nmax = std::count_if(
+ candidates.begin(), candidates.end(),
+ [maxIt] (const PrioritizedItem &i) {
+ return i.priority == maxIt->priority && i.searchPathIndex == maxIt->searchPathIndex;
+ });
+
+ if (nmax > 1) {
+ ErrorInfo e(Tr::tr("There is more than one equally prioritized candidate for module '%1'.")
+ .arg(moduleName));
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ const auto candidate = candidates.at(i);
+ if (candidate.priority == maxIt->priority) {
+ //: The %1 denotes the number of the candidate.
+ e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i).item->location());
+ }
+ }
+ throw e;
+ }
+
+ return maxIt->item;
+}
+
+ModuleLoader::Result ModuleLoader::searchAndLoadModuleFile(
+ const ProductContext &productContext, const CodeLocation &dependsItemLocation,
+ const QualifiedId &moduleName, FallbackMode fallbackMode, bool isRequired)
+{
+ 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;
+ };
+
+ SearchPathsManager searchPathsManager(d->loaderState.itemReader());
+
+ Result loadResult;
+ auto existingPaths = findExistingModulePaths();
+ 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;
+ if (result.searchPaths) {
+ qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString()
+ << "with newly added search paths from module provider";
+ d->loaderState.itemReader().pushExtraSearchPaths(*result.searchPaths);
+ existingPaths = findExistingModulePaths();
+ }
+ }
+
+ 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();
+ 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);
+ if (module)
+ candidates.emplace_back(module, 0, i);
+ if (!triedToLoad)
+ it = moduleFileNames.erase(it);
+ else
+ ++it;
+ 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.size() == 1) {
+ loadResult.moduleItem = candidates.at(0).item;
+ } else {
+ for (auto &candidate : candidates) {
+ candidate.priority = d->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());
+ }
+
+ 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();
+}
+
+std::pair<Item *, bool> ModuleLoader::Private::loadModuleFile(
+ const ProductContext &product, const QString &moduleName, const QString &filePath)
+{
+ qCDebug(lcModuleLoader) << "loadModuleFile" << moduleName << "from" << filePath;
+
+ const auto [module, triedToLoad] = getModulePrototype(product, moduleName, filePath);
+ if (!module)
+ return {nullptr, triedToLoad};
+
+ const auto key = std::make_pair(module, product.productItem);
+ const auto it = modulePrototypeEnabledInfo.find(key);
+ if (it != modulePrototypeEnabledInfo.end()) {
+ qCDebug(lcModuleLoader) << "prototype cache hit (level 2)";
+ return {it->second ? module : nullptr, triedToLoad};
+ }
+
+ if (!evaluateModuleCondition(product, module, moduleName)) {
+ qCDebug(lcModuleLoader) << "condition of module" << moduleName << "is false";
+ modulePrototypeEnabledInfo.insert({key, false});
+ return {nullptr, triedToLoad};
+ }
+
+ 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()));
+
+ const Version qbsVersion = LanguageInfo::qbsVersion();
+ module->setProperty(QStringLiteral("versionMajor"),
+ VariantValue::create(qbsVersion.majorVersion()));
+ module->setProperty(QStringLiteral("versionMinor"),
+ VariantValue::create(qbsVersion.minorVersion()));
+ module->setProperty(QStringLiteral("versionPatch"),
+ VariantValue::create(qbsVersion.patchLevel()));
+ } else {
+ Item::PropertyDeclarationMap decls;
+ const auto &moduleChildren = module->children();
+ for (Item *param : moduleChildren) {
+ if (param->type() != ItemType::Parameter)
+ 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};
+ }
+ }
+
+ 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();
+ 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()));
+ continue;
+ }
+ const PropertyDeclaration decl = module->propertyDeclaration(it.key());
+ VariantValuePtr v = VariantValue::create(
+ PropertyDeclaration::convertToPropertyType(it.value(), decl.type(),
+ QStringList(moduleName), it.key()));
+ v->markAsSetByProfile();
+ module->setProperty(it.key(), v);
+ }
+
+ return {module, true};
+}
+
+bool ModuleLoader::Private::evaluateModuleCondition(const ProductContext &product,
+ Item *module, const QString &fullModuleName)
+{
+ // Evaluator reqires module name to be set.
+ module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName));
+
+ // Temporarily make the product's qbs module instance available, so the condition
+ // can use qbs.targetOS etc.
+ class TempQbsModuleProvider {
+ public:
+ TempQbsModuleProvider(const ProductContext &product,
+ Item *module, const QString &moduleName)
+ : m_module(module), m_needsQbsItem(moduleName != StringConstants::qbsModule())
+ {
+ if (m_needsQbsItem) {
+ m_prevQbsItemValue = module->property(StringConstants::qbsModule());
+ module->setProperty(StringConstants::qbsModule(),
+ product.productItem->property(StringConstants::qbsModule()));
+ }
+ }
+ ~TempQbsModuleProvider()
+ {
+ if (!m_needsQbsItem)
+ return;
+ if (m_prevQbsItemValue)
+ m_module->setProperty(StringConstants::qbsModule(), m_prevQbsItemValue);
+ else
+ m_module->removeProperty(StringConstants::qbsModule());
+ }
+ private:
+ Item * const m_module;
+ ValuePtr m_prevQbsItemValue;
+ const bool m_needsQbsItem;
+ };
+
+ const TempQbsModuleProvider tempQbs(product, module, fullModuleName);
+ return loaderState.evaluator().boolValue(module, StringConstants::conditionProperty());
+}
+
+class DependencyParameterDeclarationCheck
+{
+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;
+};
+
+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);
+ }
+}
+
+void ModuleLoader::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;
+ d->forwardParameterDeclarations(it.key(),
+ std::static_pointer_cast<ItemValue>(it.value())->item(),
+ modules);
+ }
+}
+
+void ModuleLoader::Private::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(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);
+ }
+ }
+}
+
+void ModuleLoader::printProfilingInfo(int indent)
+{
+ 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));
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/moduleloader.h b/src/lib/corelib/loader/moduleloader.h
new file mode 100644
index 000000000..af5a8c0e9
--- /dev/null
+++ b/src/lib/corelib/loader/moduleloader.h
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+
+#include <language/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 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;
+};
+
+} // namespace Internal
+} // namespace qbs
+
diff --git a/src/lib/corelib/loader/modulepropertymerger.cpp b/src/lib/corelib/loader/modulepropertymerger.cpp
new file mode 100644
index 000000000..5ac89d776
--- /dev/null
+++ b/src/lib/corelib/loader/modulepropertymerger.cpp
@@ -0,0 +1,302 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "modulepropertymerger.h"
+
+#include "loaderutils.h"
+
+#include <language/evaluator.h>
+#include <language/item.h>
+#include <language/value.h>
+#include <logging/translator.h>
+#include <tools/profiling.h>
+#include <tools/set.h>
+#include <tools/setupprojectparameters.h>
+
+namespace qbs::Internal {
+class ModulePropertyMerger::Private
+{
+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;
+};
+
+void ModulePropertyMerger::mergeFromLocalInstance(
+ const Item *productItem, Item *loadingItem, const QString &loadingName,
+ const Item *localInstance, Item *globalInstance)
+{
+ AccumulatingTimer t(d->loaderState.parameters().logElapsedTime() ? &d->elapsedTime : nullptr);
+
+ for (auto it = localInstance->properties().constBegin();
+ it != localInstance->properties().constEnd(); ++it) {
+ d->mergePropertyFromLocalInstance(productItem, loadingItem, loadingName,
+ globalInstance, it.key(), it.value());
+ }
+}
+
+void ModulePropertyMerger::doFinalMerge(const Item *productItem)
+{
+ AccumulatingTimer t(d->loaderState.parameters().logElapsedTime() ? &d->elapsedTime : nullptr);
+
+ Set<const Item *> itemsToInvalidate;
+ for (const Item::Module &module : productItem->modules()) {
+ if (d->doFinalMerge(productItem, module.item))
+ itemsToInvalidate << module.item;
+ }
+ const auto collectDependentItems = [&itemsToInvalidate](const Item *item,
+ const auto &collect) -> bool {
+ const bool alreadyInSet = itemsToInvalidate.contains(item);
+ bool addItem = false;
+ for (const Item::Module &m : item->modules()) {
+ if (collect(m.item, collect))
+ addItem = true;
+ }
+ if (addItem && !alreadyInSet)
+ itemsToInvalidate << item;
+ return addItem || alreadyInSet;
+ };
+ collectDependentItems(productItem, collectDependentItems);
+ for (const Item * const item : itemsToInvalidate)
+ d->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)
+{
+ 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);
+ if (prio1 != prio2)
+ return prio1 - prio2;
+ const int prioDiff = v1->scopeName().compare(v2->scopeName()); // Sic! See 8ff1dd0044
+ QBS_CHECK(prioDiff != 0);
+ return prioDiff;
+}
+
+ValuePtr ModulePropertyMerger::Private::mergeListValues(
+ const Item *productItem, const ValuePtr &currentHead, const ValuePtr &newElem)
+{
+ QBS_CHECK(newElem);
+ QBS_CHECK(!newElem->next());
+
+ if (!currentHead)
+ return !newElem->expired(productItem) ? newElem : newElem->next();
+
+ QBS_CHECK(!currentHead->expired(productItem));
+
+ if (newElem->expired(productItem))
+ return currentHead;
+
+ if (compareValuePriorities(productItem, currentHead, newElem) < 0) {
+ newElem->setNext(currentHead);
+ return newElem;
+ }
+ currentHead->setNext(mergeListValues(productItem, currentHead->next(), newElem));
+ return currentHead;
+}
+
+void ModulePropertyMerger::Private::mergePropertyFromLocalInstance(
+ const Item *productItem, Item *loadingItem, const QString &loadingName, Item *globalInstance,
+ const QString &name, const ValuePtr &value)
+{
+ const PropertyDeclaration decl = globalInstance->propertyDeclaration(name);
+ if (!decl.isValid()) {
+ if (value->type() == Value::ItemValueType || value->createdByPropertiesBlock())
+ return;
+ throw ErrorInfo(Tr::tr("Property '%1' is not declared.")
+ .arg(name), value->location());
+ }
+ if (const ErrorInfo error = decl.checkForDeprecation(
+ loaderState.parameters().deprecationWarningMode(), value->location(),
+ loaderState.logger()); error.hasError()) {
+ handlePropertyError(error, loaderState.parameters(), loaderState.logger());
+ return;
+ }
+ if (value->setInternally()) { // E.g. qbs.architecture after multiplexing.
+ globalInstance->setProperty(decl.name(), value);
+ return;
+ }
+ QBS_CHECK(value->type() != Value::ItemValueType);
+ const ValuePtr globalVal = globalInstance->ownProperty(decl.name());
+ value->setScope(loadingItem, loadingName);
+ QBS_CHECK(globalVal);
+
+ // Values set internally cannot be overridden by JS values.
+ // The same goes for values set on the command line.
+ // Note that in both cases, there is no merging for list properties: The override is absolute.
+ if (globalVal->setInternally() || globalVal->setByCommandLine())
+ return;
+
+ QBS_CHECK(value->type() == Value::JSSourceValueType);
+
+ if (decl.isScalar()) {
+ QBS_CHECK(!globalVal->expired(productItem));
+ QBS_CHECK(!value->expired(productItem));
+ if (compareValuePriorities(productItem, globalVal, value) < 0) {
+ value->setCandidates(globalVal->candidates());
+ globalVal->setCandidates({});
+ value->addCandidate(globalVal);
+ globalInstance->setProperty(decl.name(), value);
+ } else {
+ globalVal->addCandidate(value);
+ }
+ } else {
+ if (const ValuePtr &newChainStart = mergeListValues(productItem, globalVal, value);
+ newChainStart != globalVal) {
+ globalInstance->setProperty(decl.name(), newChainStart);
+ }
+ }
+}
+
+bool ModulePropertyMerger::Private::doFinalMerge(const Item *productItem, 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()))
+ mustInvalidateCache = true;
+ }
+ return mustInvalidateCache;
+}
+
+bool ModulePropertyMerger::Private::doFinalMerge(
+ const Item *productItem, const PropertyDeclaration &propertyDecl, ValuePtr &propertyValue)
+{
+ if (propertyValue->type() == Value::VariantValueType) {
+ QBS_CHECK(!propertyValue->next());
+ return false;
+ }
+ if (!propertyDecl.isValid())
+ return false; // Caught later by dedicated checker.
+ propertyValue->resetPriority();
+ if (propertyDecl.isScalar()) {
+ if (propertyValue->candidates().empty())
+ return false;
+ std::pair<int, std::vector<ValuePtr>> candidatesWithHighestPrio;
+ candidatesWithHighestPrio.first = propertyValue->priority(productItem);
+ candidatesWithHighestPrio.second.push_back(propertyValue);
+ for (const ValuePtr &v : propertyValue->candidates()) {
+ const int prio = v->priority(productItem);
+ if (prio < candidatesWithHighestPrio.first)
+ continue;
+ if (prio > candidatesWithHighestPrio.first) {
+ candidatesWithHighestPrio.first = prio;
+ candidatesWithHighestPrio.second = {v};
+ continue;
+ }
+ candidatesWithHighestPrio.second.push_back(v);
+ }
+ ValuePtr chosenValue = candidatesWithHighestPrio.second.front();
+ if (int(candidatesWithHighestPrio.second.size()) > 1) {
+ ErrorInfo error(Tr::tr("Conflicting scalar values for property '%1'.")
+ .arg(propertyDecl.name()));
+ error.append({}, chosenValue->location());
+ QBS_CHECK(chosenValue->type() == Value::JSSourceValueType);
+ QStringView sourcCode = static_cast<JSSourceValue *>(
+ chosenValue.get())->sourceCode();
+ for (int i = 1; i < int(candidatesWithHighestPrio.second.size()); ++i) {
+ const ValuePtr &v = candidatesWithHighestPrio.second.at(i);
+ QBS_CHECK(v->type() == Value::JSSourceValueType);
+
+ // Note that this is a bit silly: The source code could still evaluate to
+ // different values in the end.
+ if (static_cast<JSSourceValue *>(v.get())->sourceCode() != sourcCode)
+ error.append({}, v->location());
+ }
+ if (error.items().size() > 2)
+ loaderState.logger().printWarning(error);
+ }
+
+ if (propertyValue == chosenValue)
+ return false;
+ propertyValue = chosenValue;
+ return true;
+ }
+ if (!propertyValue->next())
+ return false;
+ std::vector<ValuePtr> singleValuesBefore;
+ for (ValuePtr current = propertyValue; current;) {
+ singleValuesBefore.push_back(current);
+ const ValuePtr next = current->next();
+ if (next)
+ current->setNext({});
+ current = next;
+ }
+ ValuePtr newValue;
+ for (const ValuePtr &v : singleValuesBefore)
+ newValue = mergeListValues(productItem, newValue, v);
+ std::vector<ValuePtr> singleValuesAfter;
+ for (ValuePtr current = propertyValue; current; current = current->next())
+ singleValuesAfter.push_back(current);
+ propertyValue = newValue;
+ return singleValuesBefore != singleValuesAfter;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/modulepropertymerger.h b/src/lib/corelib/loader/modulepropertymerger.h
new file mode 100644
index 000000000..4ea72c2a4
--- /dev/null
+++ b/src/lib/corelib/loader/modulepropertymerger.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/pimpl.h>
+
+#include <QtGlobal>
+
+namespace qbs::Internal {
+class Item;
+class LoaderState;
+
+// This class comprises functions for collecting values attached to module properties
+// in different contexts.
+// For example, in the Qt.core module you will find a property binding such as this:
+// cpp.defines: "QT_CORE_LIB"
+// while in the Qt.widgets module, it will look like this:
+// cpp.defines: "QT_WIDGETS_LIB"
+// A product with a dependency on both these modules will end up with a value of
+// ["QT_WIDGETS_LIB", "QT_CORE_LIB"], plus potentially other defines set elsewhere.
+// Each of these values is assigned a priority that roughly corresponds to the "level" at which
+// the module containing the property binding resides in the dependency hierarchy.
+// For list properties, the priorities determine the order of the respecive values in the
+// final array, for scalar values they determine which one survives. Different scalar values
+// with the same priority trigger a warning message.
+// Since the right-hand side of a binding can refer to properties of the surrounding context,
+// each such value gets its own scope.
+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);
+
+private:
+ class Private;
+ Pimpl<Private> d;
+};
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/moduleproviderloader.cpp b/src/lib/corelib/loader/moduleproviderloader.cpp
index dae8bba15..06123597a 100644
--- a/src/lib/corelib/language/moduleproviderloader.cpp
+++ b/src/lib/corelib/loader/moduleproviderloader.cpp
@@ -40,21 +40,20 @@
#include "moduleproviderloader.h"
-#include "builtindeclarations.h"
-#include "evaluator.h"
#include "itemreader.h"
-#include "moduleloader.h"
#include "probesresolver.h"
+#include <language/builtindeclarations.h>
+#include <language/evaluator.h>
+#include <language/item.h>
#include <language/scriptengine.h>
#include <language/value.h>
-
#include <logging/categories.h>
#include <logging/translator.h>
-
#include <tools/fileinfo.h>
#include <tools/jsliterals.h>
#include <tools/scripttools.h>
+#include <tools/setupprojectparameters.h>
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
@@ -63,17 +62,11 @@
namespace qbs {
namespace Internal {
-ModuleProviderLoader::ModuleProviderLoader(ItemReader *reader, Evaluator *evaluator,
- ProbesResolver *probesResolver, Logger &logger)
- : m_reader(reader)
- , m_evaluator(evaluator)
- , m_probesResolver(probesResolver)
- , m_logger(logger)
-{
-}
+ModuleProviderLoader::ModuleProviderLoader(LoaderState &loaderState)
+ : m_loaderState(loaderState) {}
ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProviders(
- ProductContext &productContext,
+ const ProductContext &productContext,
const CodeLocation &dependsItemLocation,
const QualifiedId &moduleName,
FallbackMode fallbackMode)
@@ -82,7 +75,7 @@ ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModulePr
std::vector<Provider> providersToRun;
qCDebug(lcModuleLoader) << "Module" << moduleName.toString()
<< "not found, checking for module providers";
- const auto providerNames = getModuleProviders(productContext.item);
+ const auto providerNames = getModuleProviders(productContext.productItem);
if (providerNames) {
providersToRun = transformed<std::vector<Provider>>(*providerNames, [](const auto &name) {
return Provider{name, ModuleProviderLookup::Named}; });
@@ -109,7 +102,7 @@ ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModulePr
}
ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModuleProvidersHelper(
- ProductContext &product,
+ const ProductContext &product,
const CodeLocation &dependsItemLocation,
const std::vector<Provider> &providers)
{
@@ -117,9 +110,11 @@ ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModulePr
return {};
QStringList allSearchPaths;
ModuleProviderResult result;
+ result.providerConfig = product.providerConfig ? *product.providerConfig
+ : getModuleProviderConfig(product);
const auto qbsModule = evaluateQbsModule(product);
for (const auto &[name, lookupType] : providers) {
- const QVariantMap config = getModuleProviderConfig(product).value(name.toString()).toMap();
+ 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();
@@ -129,9 +124,11 @@ ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModulePr
info.providerFile = findModuleProviderFile(name, lookupType);
if (!info.providerFile.isEmpty()) {
qCDebug(lcModuleLoader) << "Running provider" << name << "at" << info.providerFile;
- info.searchPaths = evaluateModuleProvider(
- product, dependsItemLocation, name, info.providerFile, config, qbsModule);
- info.transientOutput = m_parameters.dryRun();
+ 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();
}
}
if (info.providerFile.isEmpty()) {
@@ -140,7 +137,7 @@ ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModulePr
continue;
}
if (fromCache)
- qCDebug(lcModuleLoader) << "Re-using provider" << name << "from cache";
+ qCDebug(lcModuleLoader) << "Re-using provider" << name.toString() << "from cache";
result.providerFound = true;
if (info.searchPaths.empty()) {
@@ -156,20 +153,16 @@ ModuleProviderLoader::ModuleProviderResult ModuleProviderLoader::executeModulePr
if (allSearchPaths.isEmpty())
return result;
- m_reader->pushExtraSearchPaths(allSearchPaths);
- result.providerAddedSearchPaths = true;
+ result.searchPaths = std::move(allSearchPaths);
return result;
}
-QVariantMap ModuleProviderLoader::getModuleProviderConfig(
- ProductContext &product)
+QVariantMap ModuleProviderLoader::getModuleProviderConfig(const ProductContext &product)
{
- if (product.theModuleProviderConfig)
- return *product.theModuleProviderConfig;
QVariantMap providerConfig;
const ItemValueConstPtr configItemValue =
- product.item->itemProperty(StringConstants::moduleProviders());
+ product.productItem->itemProperty(StringConstants::moduleProviders());
if (configItemValue) {
const std::function<void(const Item *, QualifiedId)> collectMap
= [this, &providerConfig, &collectMap](const Item *item, const QualifiedId &name) {
@@ -184,9 +177,9 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(
continue;
}
case Value::JSSourceValueType: {
- const ScopedJsValue sv(m_evaluator->engine()->context(),
- m_evaluator->value(item, it.key()));
- value = getJsVariant(m_evaluator->engine()->context(), sv);
+ const ScopedJsValue sv(m_loaderState.evaluator().engine()->context(),
+ m_loaderState.evaluator().value(item, it.key()));
+ value = getJsVariant(m_loaderState.evaluator().engine()->context(), sv);
break;
}
case Value::VariantValueType:
@@ -198,7 +191,7 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(
providerConfig.insert(name.toString(), m);
}
};
- configItemValue->item()->setScope(product.item);
+ configItemValue->item()->setScope(product.productItem);
collectMap(configItemValue->item(), QualifiedId());
}
for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) {
@@ -215,14 +208,14 @@ QVariantMap ModuleProviderLoader::getModuleProviderConfig(
}
providerConfig.insert(provider, currentMapForProvider);
}
- return *(product.theModuleProviderConfig = std::move(providerConfig));
+ return providerConfig;
}
std::optional<std::vector<QualifiedId>> ModuleProviderLoader::getModuleProviders(Item *item)
{
while (item) {
- const auto providers =
- m_evaluator->optionalStringListValue(item, StringConstants::qbsModuleProviders());
+ const auto providers = m_loaderState.evaluator().optionalStringListValue(
+ item, StringConstants::qbsModuleProviders());
if (providers) {
return transformed<std::vector<QualifiedId>>(*providers, [](const auto &provider) {
return QualifiedId::fromString(provider); });
@@ -235,7 +228,7 @@ std::optional<std::vector<QualifiedId>> ModuleProviderLoader::getModuleProviders
QString ModuleProviderLoader::findModuleProviderFile(
const QualifiedId &name, ModuleProviderLookup lookupType)
{
- for (const QString &path : m_reader->allSearchPaths()) {
+ for (const QString &path : m_loaderState.itemReader().allSearchPaths()) {
QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers"));
switch (lookupType) {
case ModuleProviderLookup::Named: {
@@ -265,44 +258,50 @@ QString ModuleProviderLoader::findModuleProviderFile(
return {};
}
-QVariantMap ModuleProviderLoader::evaluateQbsModule(ProductContext &product) const
+QVariantMap ModuleProviderLoader::evaluateQbsModule(const ProductContext &product) const
{
const QString properties[] = {
QStringLiteral("sysroot"),
+ QStringLiteral("toolchain"),
};
const auto qbsItemValue = std::static_pointer_cast<ItemValue>(
- product.item->property(StringConstants::qbsModule()));
+ product.productItem->property(StringConstants::qbsModule()));
QVariantMap result;
for (const auto &property : properties) {
- const ScopedJsValue val(m_evaluator->engine()->context(),
- m_evaluator->value(qbsItemValue->item(), property));
- auto value = getJsVariant(m_evaluator->engine()->context(), val);
- if (value.isValid())
- result[property] = std::move(value);
+ const ScopedJsValue val(m_loaderState.evaluator().engine()->context(),
+ m_loaderState.evaluator().value(qbsItemValue->item(), property));
+ auto value = getJsVariant(m_loaderState.evaluator().engine()->context(), val);
+ if (!value.isValid())
+ continue;
+
+ // The xcode module sets qbs.sysroot; the resulting value is bogus before the probes
+ // have run.
+ if (property == QLatin1String("sysroot") && !FileInfo::isAbsolute(value.toString()))
+ continue;
+
+ result[property] = std::move(value);
}
return result;
}
-Item *ModuleProviderLoader::createProviderScope(
- ProductContext &product, const QVariantMap &qbsModule)
+Item *ModuleProviderLoader::createProviderScope(const ProductContext &product, const QVariantMap &qbsModule)
{
const auto qbsItemValue = std::static_pointer_cast<ItemValue>(
- product.item->property(StringConstants::qbsModule()));
+ product.productItem->property(StringConstants::qbsModule()));
- Item *fakeQbsModule = Item::create(product.item->pool(), ItemType::Scope);
+ Item *fakeQbsModule = Item::create(product.productItem->pool(), 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.item->pool(), ItemType::Scope);
+ Item *scope = Item::create(product.productItem->pool(), ItemType::Scope);
scope->setFile(qbsItemValue->item()->file());
scope->setProperty(StringConstants::qbsModule(), ItemValue::create(fakeQbsModule));
return scope;
}
-QStringList ModuleProviderLoader::evaluateModuleProvider(
- ProductContext &product,
+std::pair<QStringList, std::vector<ProbeConstPtr> > ModuleProviderLoader::evaluateModuleProvider(const ProductContext &product,
const CodeLocation &dependsItemLocation,
const QualifiedId &name,
const QString &providerFile,
@@ -317,7 +316,7 @@ QStringList ModuleProviderLoader::evaluateModuleProvider(
}
m_tempQbsFiles << dummyItemFile.fileName();
qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile;
- const QString projectBuildDir = product.project->item->variantProperty(
+ const QString projectBuildDir = product.projectItem->variantProperty(
StringConstants::buildDirectoryProperty())->value().toString();
const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name);
@@ -342,8 +341,8 @@ QStringList ModuleProviderLoader::evaluateModuleProvider(
<< endl;
stream << "}" << endl;
stream.flush();
- Item * const providerItem =
- m_reader->readFile(dummyItemFile.fileName(), dependsItemLocation);
+ Item * const providerItem = m_loaderState.itemReader().setupItemFromFile(
+ dummyItemFile.fileName(), dependsItemLocation);
if (providerItem->type() != ItemType::ModuleProvider) {
throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', "
"but '%3' was expected.")
@@ -352,13 +351,16 @@ QStringList ModuleProviderLoader::evaluateModuleProvider(
}
providerItem->setScope(createProviderScope(product, qbsModule));
-
- providerItem->overrideProperties(moduleConfig, name.toString(), m_parameters, m_logger);
-
- m_probesResolver->resolveProbes(&product, providerItem);
-
- EvalContextSwitcher contextSwitcher(m_evaluator->engine(), EvalContext::ModuleProvider);
- return m_evaluator->stringListValue(providerItem, QStringLiteral("searchPaths"));
+ providerItem->overrideProperties(moduleConfig, name, m_loaderState.parameters(),
+ m_loaderState.logger());
+ std::vector<ProbeConstPtr> probes = m_loaderState.probesResolver().resolveProbes(
+ {product.name, product.uniqueName}, providerItem);
+
+ EvalContextSwitcher contextSwitcher(m_loaderState.evaluator().engine(),
+ EvalContext::ModuleProvider);
+ return std::make_pair(m_loaderState.evaluator().stringListValue(
+ providerItem, QStringLiteral("searchPaths")),
+ std::move(probes));
}
} // namespace Internal
diff --git a/src/lib/corelib/language/moduleproviderloader.h b/src/lib/corelib/loader/moduleproviderloader.h
index 91a32ec80..221830f22 100644
--- a/src/lib/corelib/language/moduleproviderloader.h
+++ b/src/lib/corelib/loader/moduleproviderloader.h
@@ -41,25 +41,25 @@
#ifndef MODULEPROVIDERLOADER_H
#define MODULEPROVIDERLOADER_H
-#include "moduleloader.h"
-#include "moduleproviderinfo.h"
-#include "probesresolver.h"
+#include "loaderutils.h"
+
+#include <language/forward_decls.h>
+#include <language/moduleproviderinfo.h>
#include <QtCore/qmap.h>
#include <QtCore/qvariant.h>
-namespace qbs {
-namespace Internal {
+#include <optional>
+#include <vector>
-class Logger;
+namespace qbs::Internal {
+class Item;
+class LoaderState;
class ModuleProviderLoader
{
public:
- using ProductContext = ModuleLoader::ProductContext;
- using FallbackMode = ModuleLoader::FallbackMode;
- explicit ModuleProviderLoader(ItemReader *itemReader, Evaluator *evaluator,
- ProbesResolver *probesResolver, Logger &logger);
+ explicit ModuleProviderLoader(LoaderState &loaderState);
enum class ModuleProviderLookup { Scoped, Named, Fallback };
@@ -71,11 +71,10 @@ public:
struct ModuleProviderResult
{
- ModuleProviderResult() = default;
- ModuleProviderResult(bool ran, bool added)
- : providerFound(ran), providerAddedSearchPaths(added) {}
+ std::vector<ProbeConstPtr> probes;
+ QVariantMap providerConfig;
bool providerFound = false;
- bool providerAddedSearchPaths = false;
+ std::optional<QStringList> searchPaths;
};
const StoredModuleProviderInfo &storedModuleProviderInfo() const
@@ -88,33 +87,36 @@ public:
m_storedModuleProviderInfo = std::move(moduleProviderInfo);
}
- void setProjectParameters(SetupProjectParameters parameters)
- {
- m_parameters = std::move(parameters);
- }
-
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(
- ProductContext &productContext,
+ const ProductContext &productContext,
const CodeLocation &dependsItemLocation,
const QualifiedId &moduleName,
FallbackMode fallbackMode);
private:
ModuleProviderResult executeModuleProvidersHelper(
- ProductContext &product,
+ const ProductContext &product,
const CodeLocation &dependsItemLocation,
const std::vector<Provider> &providers);
- QVariantMap getModuleProviderConfig(ProductContext &product);
+ QVariantMap getModuleProviderConfig(const ProductContext &product);
std::optional<std::vector<QualifiedId>> getModuleProviders(Item *item);
QString findModuleProviderFile(const QualifiedId &name, ModuleProviderLookup lookupType);
- QVariantMap evaluateQbsModule(ProductContext &product) const;
- Item *createProviderScope(ProductContext &product, const QVariantMap &qbsModule);
- QStringList evaluateModuleProvider(
- ProductContext &product,
+ QVariantMap evaluateQbsModule(const ProductContext &product) const;
+ Item *createProviderScope(const ProductContext &product, const QVariantMap &qbsModule);
+ std::pair<QStringList, std::vector<ProbeConstPtr>> evaluateModuleProvider(
+ const ProductContext &product,
const CodeLocation &location,
const QualifiedId &name,
const QString &providerFile,
@@ -122,17 +124,11 @@ private:
const QVariantMap &qbsModule);
private:
- ItemReader *const m_reader{nullptr};
- Evaluator *const m_evaluator{nullptr};
- ProbesResolver *const m_probesResolver{nullptr};
-
- SetupProjectParameters m_parameters;
- Logger &m_logger;
+ LoaderState &m_loaderState;
StoredModuleProviderInfo m_storedModuleProviderInfo;
Set<QString> m_tempQbsFiles;
};
-} // namespace Internal
-} // namespace qbs
+} // namespace qbs::Internal
#endif // MODULEPROVIDERLOADER_H
diff --git a/src/lib/corelib/language/probesresolver.cpp b/src/lib/corelib/loader/probesresolver.cpp
index d2f3fefa2..763f3ed29 100644
--- a/src/lib/corelib/language/probesresolver.cpp
+++ b/src/lib/corelib/loader/probesresolver.cpp
@@ -40,24 +40,22 @@
#include "probesresolver.h"
-#include "builtindeclarations.h"
-#include "evaluator.h"
-#include "filecontext.h"
-#include "item.h"
#include "itemreader.h"
-#include "language.h"
-#include "modulemerger.h"
-#include "qualifiedid.h"
-#include "scriptengine.h"
-#include "value.h"
+#include "loaderutils.h"
#include <api/languageinfo.h>
+#include <language/evaluator.h>
+#include <language/filecontext.h>
+#include <language/item.h>
#include <language/language.h>
+#include <language/scriptengine.h>
+#include <language/value.h>
#include <logging/categories.h>
#include <logging/logger.h>
#include <logging/translator.h>
#include <tools/profiling.h>
#include <tools/scripttools.h>
+#include <tools/setupprojectparameters.h>
#include <tools/stringconstants.h>
#include <quickjs.h>
@@ -83,18 +81,7 @@ static QString probeGlobalId(Item *probe)
return id + QLatin1Char('_') + probe->file()->filePath();
}
-ProbesResolver::ProbesResolver(Evaluator *evaluator, Logger &logger)
- : m_evaluator(evaluator)
- , m_logger(logger)
-{
-}
-
-void ProbesResolver::setProjectParameters(SetupProjectParameters parameters)
-{
- m_parameters = std::move(parameters);
- m_elapsedTimeProbes = m_probesEncountered = m_probesRun = m_probesCachedCurrent
- = m_probesCachedOld = 0;
-}
+ProbesResolver::ProbesResolver(LoaderState &loaderState) : m_loaderState(loaderState) {}
void ProbesResolver::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
{
@@ -109,17 +96,21 @@ void ProbesResolver::setOldProductProbes(
m_oldProductProbes = oldProbes;
}
-void ProbesResolver::resolveProbes(ModuleLoader::ProductContext *productContext, Item *item)
+std::vector<ProbeConstPtr> ProbesResolver::resolveProbes(const ProductContext &productContext, Item *item)
{
- AccumulatingTimer probesTimer(m_parameters.logElapsedTime() ? &m_elapsedTimeProbes : nullptr);
- EvalContextSwitcher evalContextSwitcher(m_evaluator->engine(), EvalContext::ProbeExecution);
+ AccumulatingTimer probesTimer(m_loaderState.parameters().logElapsedTime()
+ ? &m_elapsedTimeProbes : nullptr);
+ EvalContextSwitcher evalContextSwitcher(m_loaderState.evaluator().engine(),
+ EvalContext::ProbeExecution);
+ std::vector<ProbeConstPtr> probes;
for (Item * const child : item->children())
if (child->type() == ItemType::Probe)
- resolveProbe(productContext, item, child);
+ probes.push_back(resolveProbe(productContext, item, child));
+ return probes;
}
-void ProbesResolver::resolveProbe(ModuleLoader::ProductContext *productContext, Item *parent,
- Item *probe)
+ProbeConstPtr ProbesResolver::resolveProbe(const ProductContext &productContext, Item *parent,
+ Item *probe)
{
qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString();
++m_probesEncountered;
@@ -133,7 +124,8 @@ void ProbesResolver::resolveProbe(ModuleLoader::ProductContext *productContext,
throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location());
using ProbeProperty = std::pair<QString, ScopedJsValue>;
std::vector<ProbeProperty> probeBindings;
- ScriptEngine * const engine = m_evaluator->engine();
+ Evaluator &evaluator = m_loaderState.evaluator();
+ ScriptEngine * const engine = evaluator.engine();
JSContext * const ctx = engine->context();
QVariantMap initialProperties;
for (Item *obj = probe; obj; obj = obj->prototype()) {
@@ -142,22 +134,21 @@ void ProbesResolver::resolveProbe(ModuleLoader::ProductContext *productContext,
const QString &name = it.key();
if (name == StringConstants::configureProperty())
continue;
- const JSValue value = m_evaluator->value(probe, name);
+ const JSValue value = evaluator.value(probe, name);
probeBindings.emplace_back(name, ScopedJsValue(ctx, value));
if (name != StringConstants::conditionProperty())
initialProperties.insert(name, getJsVariant(ctx, value));
}
}
- const bool condition = m_evaluator->boolValue(probe, StringConstants::conditionProperty());
+ 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())) {
+ || productContext.name.startsWith(StringConstants::shadowProductPrefix())) {
resolvedProbe = findOldProjectProbe(probeId, condition, initialProperties, sourceCode);
} else {
- const QString &uniqueProductName = productContext->uniqueName();
- resolvedProbe
- = findOldProductProbe(uniqueProductName, condition, initialProperties, sourceCode);
+ resolvedProbe = findOldProductProbe(productContext.uniqueName, condition,
+ initialProperties, sourceCode);
}
if (!resolvedProbe) {
resolvedProbe = findCurrentProbe(probe->location(), condition, initialProperties);
@@ -177,7 +168,7 @@ void ProbesResolver::resolveProbe(ModuleLoader::ProductContext *productContext,
++m_probesRun;
qCDebug(lcModuleLoader) << "configure script needs to run";
const Evaluator::FileContextScopes fileCtxScopes
- = m_evaluator->fileContextScopes(configureScript->file());
+ = evaluator.fileContextScopes(configureScript->file());
configureScope.setValue(engine->newObject());
for (const ProbeProperty &b : probeBindings)
setJsProperty(ctx, configureScope, b.first, JS_DupValue(ctx, b.second));
@@ -202,7 +193,7 @@ void ProbesResolver::resolveProbe(ModuleLoader::ProductContext *productContext,
const JSValue saved = v;
ScopedJsValue valueMgr(ctx, saved);
const PropertyDeclaration decl = probe->propertyDeclaration(b.first);
- m_evaluator->convertToPropertyType(decl, probe->location(), v);
+ evaluator.convertToPropertyType(decl, probe->location(), v);
// If the value was converted from scalar to array as per our convenience
// functionality, then the original value is now the only element of a
@@ -230,7 +221,7 @@ void ProbesResolver::resolveProbe(ModuleLoader::ProductContext *productContext,
importedFilesUsedInConfigure);
m_currentProbes[probe->location()] << resolvedProbe;
}
- productContext->info.probes << resolvedProbe;
+ return resolvedProbe;
}
ProbeConstPtr ProbesResolver::findOldProjectProbe(
@@ -239,7 +230,7 @@ ProbeConstPtr ProbesResolver::findOldProjectProbe(
const QVariantMap &initialProperties,
const QString &sourceCode) const
{
- if (m_parameters.forceProbeExecution())
+ if (m_loaderState.parameters().forceProbeExecution())
return {};
for (const ProbeConstPtr &oldProbe : m_oldProjectProbes.value(globalId)) {
@@ -256,7 +247,7 @@ ProbeConstPtr ProbesResolver::findOldProductProbe(
const QVariantMap &initialProperties,
const QString &sourceCode) const
{
- if (m_parameters.forceProbeExecution())
+ if (m_loaderState.parameters().forceProbeExecution())
return {};
for (const ProbeConstPtr &oldProbe : m_oldProductProbes.value(productName)) {
@@ -291,16 +282,18 @@ bool ProbesResolver::probeMatches(const ProbeConstPtr &probe, bool condition,
&& !probe->needsReconfigure(m_lastResolveTime)));
}
-void ProbesResolver::printProfilingInfo()
+void ProbesResolver::printProfilingInfo(int indent)
{
- if (!m_parameters.logElapsedTime())
+ if (!m_loaderState.parameters().logElapsedTime())
return;
- m_logger.qbsLog(LoggerInfo, true) << "\t\t"
- << Tr::tr("Running Probes took %1.")
- .arg(elapsedTimeString(m_elapsedTimeProbes));
- m_logger.qbsLog(LoggerInfo, true) << "\t\t"
- << Tr::tr("%1 probes encountered, %2 configure scripts executed, "
- "%3 re-used from current run, %4 re-used from earlier run.")
+ 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);
}
diff --git a/src/lib/corelib/language/probesresolver.h b/src/lib/corelib/loader/probesresolver.h
index 1aeec27ce..3e304eec6 100644
--- a/src/lib/corelib/language/probesresolver.h
+++ b/src/lib/corelib/loader/probesresolver.h
@@ -41,21 +41,31 @@
#ifndef PROBESRESOLVER_H
#define PROBESRESOLVER_H
-#include "moduleloader.h"
+#include <language/forward_decls.h>
-namespace qbs {
-namespace Internal {
+#include <tools/filetime.h>
+
+#include <QString>
+
+#include <vector>
+
+namespace qbs::Internal {
+class Item;
+class LoaderState;
class ProbesResolver
{
public:
- explicit ProbesResolver(Evaluator *evaluator, Logger &logger);
- void setProjectParameters(SetupProjectParameters parameters);
+ explicit ProbesResolver(LoaderState &loaderState);
void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void resolveProbes(ModuleLoader::ProductContext *productContext, Item *item);
- void resolveProbe(ModuleLoader::ProductContext *productContext, Item *parent, Item *probe);
- void printProfilingInfo();
+ void printProfilingInfo(int indent);
+
+ struct ProductContext {
+ const QString &name;
+ const QString &uniqueName;
+ };
+ std::vector<ProbeConstPtr> resolveProbes(const ProductContext &productContext, Item *item);
private:
ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition,
@@ -70,6 +80,7 @@ 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;
@@ -77,16 +88,13 @@ private:
quint64 m_probesCachedCurrent = 0;
quint64 m_probesCachedOld = 0;
- SetupProjectParameters m_parameters;
- Evaluator *m_evaluator = nullptr;
- Logger &m_logger;
+ 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 Internal
-} // namespace qbs
+} // namespace qbs::Internal
#endif // PROBESRESOLVER_H
diff --git a/src/lib/corelib/loader/productitemmultiplexer.cpp b/src/lib/corelib/loader/productitemmultiplexer.cpp
new file mode 100644
index 000000000..f18c37caf
--- /dev/null
+++ b/src/lib/corelib/loader/productitemmultiplexer.cpp
@@ -0,0 +1,280 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "productitemmultiplexer.h"
+
+#include "loaderutils.h"
+
+#include <language/evaluator.h>
+#include <language/item.h>
+#include <language/scriptengine.h>
+#include <language/value.h>
+#include <logging/translator.h>
+#include <tools/scripttools.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <QJsonDocument>
+#include <QThreadStorage>
+
+#include <vector>
+
+
+namespace qbs::Internal {
+namespace {
+using MultiplexConfigurationByIdTable = QThreadStorage<QHash<QString, QVariantMap>>;
+using MultiplexRow = std::vector<VariantValuePtr>;
+using MultiplexTable = std::vector<MultiplexRow>;
+class MultiplexInfo
+{
+public:
+ std::vector<QString> properties;
+ MultiplexTable table;
+ bool aggregate = false;
+ VariantValuePtr multiplexedType;
+
+ QString toIdString(size_t row) const;
+};
+} // namespace
+
+Q_GLOBAL_STATIC(MultiplexConfigurationByIdTable, multiplexConfigurationsById);
+
+class ProductItemMultiplexer::Private
+{
+public:
+ Private(LoaderState &loaderState, QbsItemRetriever qbsItemRetriever)
+ : loaderState(loaderState), qbsItemRetriever(std::move(qbsItemRetriever)) {}
+
+ MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem);
+ MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values);
+
+ LoaderState &loaderState;
+ const QbsItemRetriever qbsItemRetriever;
+};
+
+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)
+{
+ const auto multiplexInfo = d->extractMultiplexInfo(productItem, tempQbsModuleItem);
+ dropTempQbsModule();
+ if (multiplexInfo.table.size() > 1)
+ productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue());
+ VariantValuePtr productNameValue = VariantValue::create(productName);
+ Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr;
+ QList<Item *> additionalProductItems;
+ std::vector<VariantValuePtr> multiplexConfigurationIdValues;
+ for (size_t row = 0; row < multiplexInfo.table.size(); ++row) {
+ Item *item = productItem;
+ const auto &mprow = multiplexInfo.table.at(row);
+ QBS_CHECK(mprow.size() == multiplexInfo.properties.size());
+ if (row > 0) {
+ item = productItem->clone();
+ additionalProductItems.push_back(item);
+ }
+ const QString multiplexConfigurationId = multiplexInfo.toIdString(row);
+ const VariantValuePtr multiplexConfigurationIdValue
+ = VariantValue::create(multiplexConfigurationId);
+ if (multiplexInfo.table.size() > 1 || aggregator) {
+ multiplexConfigurationIdValues.push_back(multiplexConfigurationIdValue);
+ item->setProperty(StringConstants::multiplexConfigurationIdProperty(),
+ multiplexConfigurationIdValue);
+ }
+ if (multiplexInfo.multiplexedType)
+ item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType);
+ for (size_t column = 0; column < mprow.size(); ++column) {
+ Item * const qbsItem = d->qbsItemRetriever(item);
+ const QString &propertyName = multiplexInfo.properties.at(column);
+ const VariantValuePtr &mpvalue = mprow.at(column);
+ qbsItem->setProperty(propertyName, mpvalue);
+ }
+ }
+
+ if (aggregator) {
+ additionalProductItems << aggregator;
+
+ // Add dependencies to all multiplexed instances.
+ for (const auto &v : multiplexConfigurationIdValues) {
+ Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends);
+ dependsItem->setProperty(StringConstants::nameProperty(), productNameValue);
+ dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), v);
+ dependsItem->setProperty(StringConstants::profilesProperty(),
+ VariantValue::create(QStringList()));
+ dependsItem->setFile(aggregator->file());
+ dependsItem->setupForBuiltinType(d->loaderState.parameters().deprecationWarningMode(),
+ d->loaderState.logger());
+ Item::addChild(aggregator, dependsItem);
+ }
+ }
+
+ return additionalProductItems;
+}
+
+MultiplexInfo ProductItemMultiplexer::Private::extractMultiplexInfo(Item *productItem,
+ Item *qbsModuleItem)
+{
+ static const QString mpmKey = QStringLiteral("multiplexMap");
+
+ Evaluator &evaluator = loaderState.evaluator();
+ JSContext * const ctx = evaluator.engine()->context();
+ const ScopedJsValue multiplexMap(ctx, evaluator.value(qbsModuleItem, mpmKey));
+ const QStringList multiplexByQbsProperties = evaluator.stringListValue(
+ productItem, StringConstants::multiplexByQbsPropertiesProperty());
+
+ MultiplexInfo multiplexInfo;
+ multiplexInfo.aggregate = evaluator.boolValue(
+ productItem, StringConstants::aggregateProperty());
+
+ const QString multiplexedType = evaluator.stringValue(
+ productItem, StringConstants::multiplexedTypeProperty());
+ if (!multiplexedType.isEmpty())
+ multiplexInfo.multiplexedType = VariantValue::create(multiplexedType);
+
+ Set<QString> uniqueMultiplexByQbsProperties;
+ for (const QString &key : multiplexByQbsProperties) {
+ const QString mappedKey = getJsStringProperty(ctx, multiplexMap, key);
+ if (mappedKey.isEmpty())
+ throw ErrorInfo(Tr::tr("There is no entry for '%1' in 'qbs.multiplexMap'.").arg(key));
+
+ if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second)
+ continue;
+
+ const ScopedJsValue arr(ctx, evaluator.value(qbsModuleItem, key));
+ if (JS_IsUndefined(arr))
+ continue;
+ if (!JS_IsArray(ctx, arr))
+ throw ErrorInfo(Tr::tr("Property '%1' must be an array.").arg(key));
+
+ const quint32 arrlen = getJsIntProperty(ctx, arr, StringConstants::lengthProperty());
+ if (arrlen == 0)
+ continue;
+
+ MultiplexRow mprow;
+ mprow.reserve(arrlen);
+ QVariantList entriesForKey;
+ for (quint32 i = 0; i < arrlen; ++i) {
+ const ScopedJsValue sv(ctx, JS_GetPropertyUint32(ctx, arr, i));
+ const QVariant value = getJsVariant(ctx, sv);
+ if (entriesForKey.contains(value))
+ continue;
+ entriesForKey << value;
+ mprow.push_back(VariantValue::create(value));
+ }
+ multiplexInfo.table = combine(multiplexInfo.table, mprow);
+ multiplexInfo.properties.push_back(mappedKey);
+ }
+ return multiplexInfo;
+}
+
+MultiplexTable ProductItemMultiplexer::Private::combine(const MultiplexTable &table,
+ const MultiplexRow &values)
+{
+ MultiplexTable result;
+ if (table.empty()) {
+ result.resize(values.size());
+ for (size_t i = 0; i < values.size(); ++i) {
+ MultiplexRow row;
+ row.resize(1);
+ row[0] = values.at(i);
+ result[i] = row;
+ }
+ } else {
+ for (const auto &row : table) {
+ for (const auto &value : values) {
+ MultiplexRow newRow = row;
+ newRow.push_back(value);
+ result.push_back(newRow);
+ }
+ }
+ }
+ return result;
+}
+
+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
+{
+ const auto &mprow = table.at(row);
+ QVariantMap multiplexConfiguration;
+ for (size_t column = 0; column < mprow.size(); ++column) {
+ const QString &propertyName = properties.at(column);
+ const VariantValuePtr &mpvalue = mprow.at(column);
+ multiplexConfiguration.insert(propertyName, mpvalue->value());
+ }
+ QString id = QString::fromUtf8(QJsonDocument::fromVariant(multiplexConfiguration)
+ .toJson(QJsonDocument::Compact)
+ .toBase64());
+
+ // Cache for later use in multiplexIdToVariantMap()
+ multiplexConfigurationsById->localData().insert(id, multiplexConfiguration);
+
+ return id;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productitemmultiplexer.h b/src/lib/corelib/loader/productitemmultiplexer.h
new file mode 100644
index 000000000..53eb1a722
--- /dev/null
+++ b/src/lib/corelib/loader/productitemmultiplexer.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/pimpl.h>
+
+#include <QList>
+#include <QVariantMap>
+
+#include <functional>
+
+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;
+};
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productscollector.cpp b/src/lib/corelib/loader/productscollector.cpp
new file mode 100644
index 000000000..95b248c73
--- /dev/null
+++ b/src/lib/corelib/loader/productscollector.cpp
@@ -0,0 +1,736 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "productscollector.h"
+
+#include "dependenciesresolver.h"
+#include "itemreader.h"
+#include "loaderutils.h"
+#include "localprofiles.h"
+#include "productitemmultiplexer.h"
+#include "probesresolver.h"
+
+#include <language/builtindeclarations.h>
+#include <language/evaluator.h>
+#include <language/filecontext.h>
+#include <language/item.h>
+#include <language/language.h>
+#include <language/value.h>
+#include <logging/categories.h>
+#include <logging/translator.h>
+#include <tools/fileinfo.h>
+#include <tools/profile.h>
+#include <tools/profiling.h>
+#include <language/scriptengine.h>
+#include <tools/set.h>
+#include <tools/settings.h>
+#include <tools/setupprojectparameters.h>
+#include <tools/stringconstants.h>
+
+#include <QDir>
+#include <QDirIterator>
+
+namespace qbs::Internal {
+
+class ProductsCollector::Private
+{
+public:
+ Private(LoaderState &loaderState) : loaderState(loaderState) {}
+
+ void handleProject(Item *projectItem, const Set<QString> &referencedFilePaths);
+ QList<Item *> multiplexProductItem(ProductContext &dummyContext, Item *productItem);
+ void prepareProduct(ProjectContext &projectContext, Item *productItem);
+ void handleSubProject(ProjectContext &projectContext, Item *projectItem,
+ const Set<QString> &referencedFilePaths);
+ void copyProperties(const Item *sourceProject, Item *targetProject);
+ QList<Item *> loadReferencedFile(
+ const QString &relativePath, const CodeLocation &referencingLocation,
+ const Set<QString> &referencedFilePaths, ProductContext &dummyContext);
+ bool mergeExportItems(ProductContext &productContext);
+ bool checkExportItemCondition(Item *exportItem, const ProductContext &product);
+ void initProductProperties(const ProductContext &product);
+ void checkProjectNamesInOverrides();
+ void collectProductsByName();
+ void checkProductNamesInOverrides();
+
+ LoaderState &loaderState;
+ Settings settings{loaderState.parameters().settingsDirectory()};
+ Set<QString> disabledProjects;
+ Version qbsVersion;
+ Item *tempScopeItem = nullptr;
+ qint64 elapsedTimePrepareProducts = 0;
+
+private:
+ class TempBaseModuleAttacher {
+ public:
+ TempBaseModuleAttacher(Private *d, ProductContext &product);
+ ~TempBaseModuleAttacher() { drop(); }
+ void drop();
+ Item *tempBaseModuleItem() const { return m_tempBaseModule; }
+
+ private:
+ Item * const m_productItem;
+ ValuePtr m_origQbsValue;
+ Item *m_tempBaseModule = nullptr;
+ };
+};
+
+ProductsCollector::ProductsCollector(LoaderState &loaderState)
+ : d(makePimpl<Private>(loaderState)) {}
+
+ProductsCollector::~ProductsCollector() = default;
+
+void ProductsCollector::run(Item *rootProject)
+{
+ d->handleProject(rootProject, {QDir::cleanPath(d->loaderState.parameters().projectFilePath())});
+ d->checkProjectNamesInOverrides();
+ d->collectProductsByName();
+ d->checkProductNamesInOverrides();
+}
+
+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,
+ const Set<QString> &referencedFilePaths)
+{
+ const SetupProjectParameters &parameters = loaderState.parameters();
+ Evaluator &evaluator = loaderState.evaluator();
+ Logger &logger = loaderState.logger();
+ ItemReader &itemReader = loaderState.itemReader();
+ TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
+
+ auto p = std::make_unique<ProjectContext>();
+ auto &projectContext = *p;
+ projectContext.topLevelProject = &topLevelProject;
+ ItemValuePtr itemValue = ItemValue::create(projectItem);
+ projectContext.scope = Item::create(projectItem->pool(), ItemType::Scope);
+ projectContext.scope->setFile(projectItem->file());
+ projectContext.scope->setProperty(StringConstants::projectVar(), itemValue);
+ ProductContext dummyProduct;
+ dummyProduct.project = &projectContext;
+ dummyProduct.moduleProperties = parameters.finalBuildConfigurationTree();
+ dummyProduct.profileModuleProperties = dummyProduct.moduleProperties;
+ dummyProduct.profileName = parameters.topLevelProfile();
+ loaderState.dependenciesResolver().loadBaseModule(dummyProduct, projectItem);
+
+ projectItem->overrideProperties(parameters.overriddenValuesTree(),
+ StringConstants::projectPrefix(), parameters, logger);
+ projectContext.name = evaluator.stringValue(projectItem, StringConstants::nameProperty());
+ if (projectContext.name.isEmpty()) {
+ projectContext.name = FileInfo::baseName(projectItem->location().filePath());
+ projectItem->setProperty(StringConstants::nameProperty(),
+ VariantValue::create(projectContext.name));
+ }
+ projectItem->overrideProperties(parameters.overriddenValuesTree(),
+ StringConstants::projectsOverridePrefix() + projectContext.name,
+ parameters, logger);
+ if (!topLevelProject.checkItemCondition(projectItem, evaluator)) {
+ 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(),
+ QStringLiteral("1.3.0"));
+ const Version minVersion = Version::fromString(minVersionStr);
+ if (!minVersion.isValid()) {
+ throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion "
+ "is not a valid version string.")
+ .arg(minVersionStr), projectItem->location());
+ }
+ if (!qbsVersion.isValid())
+ qbsVersion = Version::fromString(QLatin1String(QBS_VERSION));
+ if (qbsVersion < minVersion) {
+ throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but "
+ "this is qbs version %2.").arg(minVersion.toString(),
+ qbsVersion.toString()));
+ }
+
+ for (Item * const child : projectItem->children())
+ child->setScope(projectContext.scope);
+
+ projectContext.topLevelProject->probes << loaderState.probesResolver().resolveProbes(
+ {dummyProduct.name, dummyProduct.uniqueName()}, projectItem);
+
+ loaderState.localProfiles().collectProfilesFromItems(projectItem, projectContext.scope);
+
+ QList<Item *> multiplexedProducts;
+ for (Item * const child : projectItem->children()) {
+ if (child->type() == ItemType::Product)
+ multiplexedProducts << multiplexProductItem(dummyProduct, child);
+ }
+ for (Item * const additionalProductItem : std::as_const(multiplexedProducts))
+ Item::addChild(projectItem, additionalProductItem);
+
+ const QList<Item *> originalChildren = projectItem->children();
+ for (Item * const child : originalChildren) {
+ switch (child->type()) {
+ case ItemType::Product:
+ prepareProduct(projectContext, child);
+ break;
+ case ItemType::SubProject:
+ handleSubProject(projectContext, child, referencedFilePaths);
+ break;
+ case ItemType::Project:
+ copyProperties(projectItem, child);
+ handleProject(child, referencedFilePaths);
+ break;
+ default:
+ break;
+ }
+ }
+
+ const QStringList refs = evaluator.stringListValue(
+ projectItem, StringConstants::referencesProperty());
+ const CodeLocation referencingLocation
+ = projectItem->property(StringConstants::referencesProperty())->location();
+ QList<Item *> additionalProjectChildren;
+ for (const QString &filePath : refs) {
+ try {
+ additionalProjectChildren << loadReferencedFile(
+ filePath, referencingLocation, referencedFilePaths, dummyProduct);
+ } catch (const ErrorInfo &error) {
+ if (parameters.productErrorMode() == ErrorHandlingMode::Strict)
+ throw;
+ logger.printWarning(error);
+ }
+ }
+ for (Item * const subItem : std::as_const(additionalProjectChildren)) {
+ Item::addChild(projectContext.item, subItem);
+ switch (subItem->type()) {
+ case ItemType::Product:
+ prepareProduct(projectContext, subItem);
+ break;
+ case ItemType::Project:
+ copyProperties(projectItem, subItem);
+ handleProject(subItem,
+ Set<QString>(referencedFilePaths) << subItem->file()->filePath());
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+QList<Item *> ProductsCollector::Private::multiplexProductItem(ProductContext &dummyContext,
+ Item *productItem)
+{
+ // Overriding the product item properties must be done here already, because multiplexing
+ // properties might depend on product properties.
+ const QString &nameKey = StringConstants::nameProperty();
+ QString productName = loaderState.evaluator().stringValue(productItem, nameKey);
+ if (productName.isEmpty()) {
+ productName = FileInfo::completeBaseName(productItem->file()->filePath());
+ productItem->setProperty(nameKey, VariantValue::create(productName));
+ }
+ productItem->overrideProperties(loaderState.parameters().overriddenValuesTree(),
+ StringConstants::productsOverridePrefix() + productName,
+ loaderState.parameters(), loaderState.logger());
+ dummyContext.item = productItem;
+ TempBaseModuleAttacher tbma(this, dummyContext);
+ return loaderState.multiplexer().multiplex(productName, productItem, tbma.tempBaseModuleItem(),
+ [&] { tbma.drop(); });
+}
+
+void ProductsCollector::Private::prepareProduct(ProjectContext &projectContext, Item *productItem)
+{
+ const SetupProjectParameters &parameters = loaderState.parameters();
+ Evaluator &evaluator = loaderState.evaluator();
+ TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
+
+ AccumulatingTimer timer(parameters.logElapsedTime() ? &elapsedTimePrepareProducts : nullptr);
+ topLevelProject.checkCancelation(parameters);
+ qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath();
+
+ ProductContext productContext;
+ 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());
+ if (qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) {
+ TempBaseModuleAttacher tbma(this, productContext);
+ productContext.profileName = evaluator.stringValue(
+ tbma.tempBaseModuleItem(), StringConstants::profileProperty(), QString());
+ } else {
+ productContext.profileName = parameters.topLevelProfile();
+ }
+ productContext.multiplexConfigurationId = evaluator.stringValue(
+ productItem, StringConstants::multiplexConfigurationIdProperty());
+ QBS_CHECK(!productContext.profileName.isEmpty());
+
+ // Set up full module property map based on the profile.
+ const auto it = topLevelProject.profileConfigs.constFind(productContext.profileName);
+ QVariantMap flatConfig;
+ if (it == topLevelProject.profileConfigs.constEnd()) {
+ const Profile profile(productContext.profileName, &settings,
+ loaderState.localProfiles().profiles());
+ if (!profile.exists()) {
+ ErrorInfo error(Tr::tr("Profile '%1' does not exist.").arg(profile.name()),
+ productItem->location());
+ productContext.handleError(error);
+ return;
+ }
+ flatConfig = SetupProjectParameters::expandedBuildConfiguration(
+ profile, parameters.configurationName());
+ topLevelProject.profileConfigs.insert(productContext.profileName, flatConfig);
+ } else {
+ flatConfig = it.value().toMap();
+ }
+ productContext.profileModuleProperties = SetupProjectParameters::finalBuildConfigurationTree(
+ flatConfig, {});
+ productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree(
+ flatConfig, parameters.overriddenValues());
+ initProductProperties(productContext);
+
+ // Set up product scope. This is mainly for using the "product" and "project"
+ // variables in some contexts.
+ ItemValuePtr itemValue = ItemValue::create(productItem);
+ productContext.scope = Item::create(productItem->pool(), ItemType::Scope);
+ productContext.scope->setProperty(StringConstants::productVar(), itemValue);
+ productContext.scope->setFile(productItem->file());
+ productContext.scope->setScope(productContext.project->scope);
+
+ const bool hasExportItems = mergeExportItems(productContext);
+
+ setScopeForDescendants(productItem, productContext.scope);
+
+ projectContext.products.push_back(productContext);
+
+ if (!hasExportItems || getShadowProductInfo(productContext).first)
+ return;
+
+ // This "shadow product" exists only to pull in a dependency on the actual product
+ // and nothing else, thus providing us with the pure environment that we need to
+ // evaluate the product's exported properties in isolation in the project resolver.
+ Item * const importer = Item::create(productItem->pool(), ItemType::Product);
+ importer->setProperty(QStringLiteral("name"),
+ VariantValue::create(StringConstants::shadowProductPrefix()
+ + productContext.name));
+ importer->setFile(productItem->file());
+ importer->setLocation(productItem->location());
+ importer->setScope(projectContext.scope);
+ importer->setupForBuiltinType(parameters.deprecationWarningMode(), loaderState.logger());
+ Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends);
+ dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name));
+ dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false));
+ dependsItem->setFile(importer->file());
+ dependsItem->setLocation(importer->location());
+ dependsItem->setupForBuiltinType(parameters.deprecationWarningMode(), loaderState.logger());
+ Item::addChild(importer, dependsItem);
+ Item::addChild(productItem, importer);
+ prepareProduct(projectContext, importer);
+}
+
+void ProductsCollector::Private::handleSubProject(
+ ProjectContext &projectContext, Item *projectItem, const Set<QString> &referencedFilePaths)
+{
+ const SetupProjectParameters &parameters = loaderState.parameters();
+ Evaluator &evaluator = loaderState.evaluator();
+ Logger &logger = loaderState.logger();
+ ItemReader &itemReader = loaderState.itemReader();
+ TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
+
+ qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath();
+
+ Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject);
+ if (!topLevelProject.checkItemCondition(projectItem, evaluator))
+ return;
+ if (propertiesItem) {
+ propertiesItem->setScope(projectItem);
+ if (!topLevelProject.checkItemCondition(propertiesItem, evaluator))
+ return;
+ }
+
+ Item *loadedItem = nullptr;
+ QString subProjectFilePath;
+ try {
+ const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath());
+ const QString relativeFilePath
+ = evaluator.stringValue(projectItem, StringConstants::filePathProperty());
+ subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath);
+ if (referencedFilePaths.contains(subProjectFilePath))
+ throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.")
+ .arg(relativeFilePath), projectItem->location());
+ loadedItem = itemReader.setupItemFromFile(subProjectFilePath, projectItem->location());
+ } catch (const ErrorInfo &error) {
+ if (parameters.productErrorMode() == ErrorHandlingMode::Strict)
+ throw;
+ logger.printWarning(error);
+ return;
+ }
+
+ loadedItem = itemReader.wrapInProjectIfNecessary(loadedItem);
+ const bool inheritProperties = evaluator.boolValue(
+ projectItem, StringConstants::inheritPropertiesProperty());
+
+ if (inheritProperties)
+ copyProperties(projectItem->parent(), loadedItem);
+ if (propertiesItem) {
+ const Item::PropertyMap &overriddenProperties = propertiesItem->properties();
+ for (auto it = overriddenProperties.begin(); it != overriddenProperties.end(); ++it)
+ loadedItem->setProperty(it.key(), it.value());
+ }
+
+ Item::addChild(projectItem, loadedItem);
+ projectItem->setScope(projectContext.scope);
+ handleProject(loadedItem, Set<QString>(referencedFilePaths) << subProjectFilePath);
+}
+
+void ProductsCollector::Private::copyProperties(const Item *sourceProject, Item *targetProject)
+{
+ if (!sourceProject)
+ return;
+ const QList<PropertyDeclaration> builtinProjectProperties
+ = BuiltinDeclarations::instance().declarationsForType(ItemType::Project).properties();
+ Set<QString> builtinProjectPropertyNames;
+ for (const PropertyDeclaration &p : builtinProjectProperties)
+ builtinProjectPropertyNames << p.name();
+
+ for (auto it = sourceProject->propertyDeclarations().begin();
+ it != sourceProject->propertyDeclarations().end(); ++it) {
+
+ // We must not inherit built-in properties such as "name",
+ // but there are exceptions.
+ if (it.key() == StringConstants::qbsSearchPathsProperty()
+ || it.key() == StringConstants::profileProperty()
+ || it.key() == StringConstants::buildDirectoryProperty()
+ || it.key() == StringConstants::sourceDirectoryProperty()
+ || it.key() == StringConstants::minimumQbsVersionProperty()) {
+ const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key());
+ QBS_ASSERT(v, continue);
+ if (v->sourceCode() == StringConstants::undefinedValue())
+ sourceProject->copyProperty(it.key(), targetProject);
+ continue;
+ }
+
+ if (builtinProjectPropertyNames.contains(it.key()))
+ continue;
+
+ if (targetProject->hasOwnProperty(it.key()))
+ continue; // Ignore stuff the target project already has.
+
+ targetProject->setPropertyDeclaration(it.key(), it.value());
+ sourceProject->copyProperty(it.key(), targetProject);
+ }
+}
+
+QList<Item *> ProductsCollector::Private::loadReferencedFile(
+ const QString &relativePath, const CodeLocation &referencingLocation,
+ const Set<QString> &referencedFilePaths, ProductContext &dummyContext)
+{
+ QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()),
+ relativePath);
+ if (FileInfo(absReferencePath).isDir()) {
+ QString qbsFilePath;
+
+ QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards());
+ while (dit.hasNext()) {
+ if (!qbsFilePath.isEmpty()) {
+ throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one "
+ "qbs file.").arg(absReferencePath), referencingLocation);
+ }
+ qbsFilePath = dit.next();
+ }
+ if (qbsFilePath.isEmpty()) {
+ throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.")
+ .arg(absReferencePath), referencingLocation);
+ }
+ absReferencePath = qbsFilePath;
+ }
+ if (referencedFilePaths.contains(absReferencePath))
+ throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath),
+ referencingLocation);
+ Item * const subItem = loaderState.itemReader().setupItemFromFile(
+ absReferencePath, referencingLocation);
+ if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) {
+ ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.")
+ .arg(subItem->typeName()));
+ error.append(Tr::tr("Item is defined here."), subItem->location());
+ error.append(Tr::tr("File is referenced here."), referencingLocation);
+ throw error;
+ }
+ subItem->setScope(dummyContext.project->scope);
+ subItem->setParent(dummyContext.project->item);
+ QList<Item *> loadedItems;
+ loadedItems << subItem;
+ if (subItem->type() == ItemType::Product) {
+ loaderState.localProfiles().collectProfilesFromItems(subItem, dummyContext.project->scope);
+ loadedItems << multiplexProductItem(dummyContext, subItem);
+ }
+ return loadedItems;
+}
+
+static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value)
+{
+ if (value->type() == Value::ItemValueType) {
+ const ItemValueConstPtr itemValue = std::static_pointer_cast<ItemValue>(value);
+ const Item * const valueItem = itemValue->item();
+ Item * const subItem = dst->itemProperty(name, itemValue)->item();
+ for (auto it = valueItem->properties().begin(); it != valueItem->properties().end(); ++it)
+ mergeProperty(subItem, it.key(), it.value());
+ return;
+ }
+
+ // If the property already exists, set up the base value.
+ if (value->type() == Value::JSSourceValueType) {
+ const auto jsValue = static_cast<JSSourceValue *>(value.get());
+ if (jsValue->isBuiltinDefaultValue())
+ return;
+ const ValuePtr baseValue = dst->property(name);
+ if (baseValue) {
+ QBS_CHECK(baseValue->type() == Value::JSSourceValueType);
+ const JSSourceValuePtr jsBaseValue = std::static_pointer_cast<JSSourceValue>(
+ baseValue->clone());
+ jsValue->setBaseValue(jsBaseValue);
+ std::vector<JSSourceValue::Alternative> alternatives = jsValue->alternatives();
+ jsValue->clearAlternatives();
+ for (JSSourceValue::Alternative &a : alternatives) {
+ a.value->setBaseValue(jsBaseValue);
+ jsValue->addAlternative(a);
+ }
+ }
+ }
+ dst->setProperty(name, value);
+}
+
+bool ProductsCollector::Private::mergeExportItems(ProductContext &productContext)
+{
+ const SetupProjectParameters &parameters = loaderState.parameters();
+ Evaluator &evaluator = loaderState.evaluator();
+ Logger &logger = loaderState.logger();
+ TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
+
+ std::vector<Item *> exportItems;
+ QList<Item *> children = productContext.item->children();
+
+ const auto isExport = [](Item *item) { return item->type() == ItemType::Export; };
+ std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport);
+ qbs::Internal::removeIf(children, isExport);
+
+ // Note that we do not return if there are no Export items: The "merged" item becomes the
+ // "product module", which always needs to exist, regardless of whether the product sources
+ // actually contain an Export item or not.
+ if (!exportItems.empty())
+ productContext.item->setChildren(children);
+
+ Item *merged = Item::create(productContext.item->pool(), 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);
+ if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file())))
+ throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."),
+ exportItem->location());
+ exportItem->setProperty(nameKey, nameValue);
+ if (!checkExportItemCondition(exportItem, productContext))
+ continue;
+ filesWithExportItem += exportItem->file();
+ for (Item * const child : exportItem->children()) {
+ if (child->type() == ItemType::Parameters) {
+ adjustParametersScopes(child, child);
+ mergeParameters(defaultParameters,
+ getJsVariant(evaluator.engine()->context(),
+ evaluator.scriptValue(child)).toMap());
+ } else {
+ Item::addChild(merged, child);
+ }
+ }
+ const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations();
+ for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) {
+ const PropertyDeclaration &newDecl = it.value();
+ const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key());
+ if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) {
+ ErrorInfo error(Tr::tr("Export item in inherited item redeclares property "
+ "'%1' with different type.").arg(it.key()), exportItem->location());
+ handlePropertyError(error, parameters, logger);
+ }
+ merged->setPropertyDeclaration(newDecl.name(), newDecl);
+ }
+ for (QMap<QString, ValuePtr>::const_iterator it = exportItem->properties().constBegin();
+ it != exportItem->properties().constEnd(); ++it) {
+ mergeProperty(merged, it.key(), it.value());
+ }
+ }
+ merged->setFile(exportItems.empty()
+ ? productContext.item->file() : exportItems.back()->file());
+ merged->setLocation(exportItems.empty()
+ ? productContext.item->location() : exportItems.back()->location());
+ Item::addChild(productContext.item, merged);
+ merged->setupForBuiltinType(parameters.deprecationWarningMode(), logger);
+
+ productContext.mergedExportItem = merged;
+ productContext.defaultParameters = defaultParameters;
+ return !exportItems.empty();
+
+}
+
+// TODO: This seems dubious. Can we merge the conditions instead?
+bool ProductsCollector::Private::checkExportItemCondition(Item *exportItem,
+ const ProductContext &product)
+{
+ class ScopeHandler {
+ public:
+ ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem)
+ : m_exportItem(exportItem)
+ {
+ if (!*cachedScopeItem)
+ *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope);
+ Item * const scope = *cachedScopeItem;
+ QBS_CHECK(productContext.item->file());
+ scope->setFile(productContext.item->file());
+ scope->setScope(productContext.item);
+ productContext.project->scope->copyProperty(StringConstants::projectVar(), scope);
+ productContext.scope->copyProperty(StringConstants::productVar(), scope);
+ QBS_CHECK(!exportItem->scope());
+ exportItem->setScope(scope);
+ }
+ ~ScopeHandler() { m_exportItem->setScope(nullptr); }
+
+ private:
+ Item * const m_exportItem;
+ } scopeHandler(exportItem, product, &tempScopeItem);
+ return loaderState.topLevelProject().checkItemCondition(exportItem, loaderState.evaluator());
+}
+
+void ProductsCollector::Private::initProductProperties(const ProductContext &product)
+{
+ QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name,
+ product.multiplexConfigurationId);
+ buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir);
+ product.item->setProperty(StringConstants::buildDirectoryProperty(),
+ VariantValue::create(buildDir));
+ const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath();
+ product.item->setProperty(StringConstants::sourceDirectoryProperty(),
+ VariantValue::create(sourceDir));
+}
+
+void ProductsCollector::Private::checkProjectNamesInOverrides()
+{
+ for (const QString &projectNameInOverride
+ : loaderState.topLevelProject().projectNamesUsedInOverrides) {
+ if (disabledProjects.contains(projectNameInOverride))
+ continue;
+ if (!any_of(loaderState.topLevelProject().projects,
+ [&projectNameInOverride](const ProjectContext *p) {
+ return p->name == projectNameInOverride; })) {
+ handlePropertyError(Tr::tr("Unknown project '%1' in property override.")
+ .arg(projectNameInOverride),
+ loaderState.parameters(), loaderState.logger());
+ }
+ }
+}
+
+void ProductsCollector::Private::collectProductsByName()
+{
+ TopLevelProjectContext &topLevelProject = loaderState.topLevelProject();
+ for (ProjectContext * const project : topLevelProject.projects) {
+ for (ProductContext &product : project->products)
+ topLevelProject.productsByName.insert({product.name, &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) {
+ // 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());
+ })) {
+ handlePropertyError(Tr::tr("Unknown product '%1' in property override.")
+ .arg(productNameInOverride),
+ loaderState.parameters(), loaderState.logger());
+ }
+ }
+}
+
+ProductsCollector::Private::TempBaseModuleAttacher::TempBaseModuleAttacher(
+ ProductsCollector::Private *d, ProductContext &product)
+ : m_productItem(product.item)
+{
+ const ValuePtr qbsValue = m_productItem->property(StringConstants::qbsModule());
+
+ // Cloning is necessary because the original value will get "instantiated" now.
+ if (qbsValue)
+ m_origQbsValue = qbsValue->clone();
+
+ m_tempBaseModule = d->loaderState.dependenciesResolver().loadBaseModule(product, m_productItem);
+}
+
+void ProductsCollector::Private::TempBaseModuleAttacher::drop()
+{
+ if (!m_tempBaseModule)
+ return;
+
+ // "Unload" the qbs module again.
+ if (m_origQbsValue)
+ m_productItem->setProperty(StringConstants::qbsModule(), m_origQbsValue);
+ else
+ m_productItem->removeProperty(StringConstants::qbsModule());
+ m_productItem->removeModules();
+ m_tempBaseModule = nullptr;
+}
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productscollector.h b/src/lib/corelib/loader/productscollector.h
new file mode 100644
index 000000000..fa2059872
--- /dev/null
+++ b/src/lib/corelib/loader/productscollector.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/pimpl.h>
+
+namespace qbs::Internal {
+class Item;
+class LoaderState;
+
+// Traverses the root project item and fills the TopLevelProjectContext with all the
+// product and sub-project information, including those coming from referenced files.
+class ProductsCollector
+{
+public:
+ ProductsCollector(LoaderState &loaderState);
+ ~ProductsCollector();
+
+ void run(Item *rootProject);
+ void printProfilingInfo(int indent);
+
+private:
+ class Private;
+ Pimpl<Private> d;
+};
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/loader/productshandler.cpp b/src/lib/corelib/loader/productshandler.cpp
new file mode 100644
index 000000000..e0ca79acc
--- /dev/null
+++ b/src/lib/corelib/loader/productshandler.cpp
@@ -0,0 +1,336 @@
+/****************************************************************************
+**
+** 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/productshandler.h b/src/lib/corelib/loader/productshandler.h
new file mode 100644
index 000000000..f1fc74a20
--- /dev/null
+++ b/src/lib/corelib/loader/productshandler.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#pragma once
+
+#include <tools/pimpl.h>
+
+namespace qbs::Internal {
+class LoaderState;
+
+// 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);
+
+private:
+ class Private;
+ Pimpl<Private> d;
+};
+
+} // namespace qbs::Internal
diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/loader/projectresolver.cpp
index c2c8ba134..d771e632c 100644
--- a/src/lib/corelib/language/projectresolver.cpp
+++ b/src/lib/corelib/loader/projectresolver.cpp
@@ -39,30 +39,36 @@
#include "projectresolver.h"
-#include "artifactproperties.h"
-#include "builtindeclarations.h"
-#include "evaluator.h"
-#include "filecontext.h"
-#include "item.h"
-#include "language.h"
-#include "propertymapinternal.h"
-#include "resolvedfilecontext.h"
-#include "scriptengine.h"
-#include "value.h"
+#include "projecttreebuilder.h"
#include <jsextensions/jsextensions.h>
#include <jsextensions/moduleproperties.h>
+#include <language/artifactproperties.h>
+#include <language/builtindeclarations.h>
+#include <language/evaluator.h>
+#include <language/filecontext.h>
+#include <language/filetags.h>
+#include <language/item.h>
+#include <language/itempool.h>
+#include <language/language.h>
+#include <language/propertymapinternal.h>
+#include <language/resolvedfilecontext.h>
+#include <language/scriptengine.h>
+#include <language/value.h>
#include <logging/categories.h>
#include <logging/translator.h>
#include <tools/error.h>
#include <tools/fileinfo.h>
#include <tools/joblimits.h>
#include <tools/jsliterals.h>
+#include <tools/profile.h>
#include <tools/profiling.h>
#include <tools/progressobserver.h>
#include <tools/scripttools.h>
#include <tools/qbsassert.h>
#include <tools/qttools.h>
+#include <tools/set.h>
+#include <tools/settings.h>
#include <tools/setupprojectparameters.h>
#include <tools/stlutils.h>
#include <tools/stringconstants.h>
@@ -75,6 +81,8 @@
#include <algorithm>
#include <memory>
#include <queue>
+#include <utility>
+#include <vector>
namespace qbs {
namespace Internal {
@@ -87,9 +95,9 @@ static const FileTag unknownFileTag()
return tag;
}
-struct ProjectResolver::ProjectContext
+struct ResolverProjectContext
{
- ProjectContext *parentContext = nullptr;
+ ResolverProjectContext *parentContext = nullptr;
ResolvedProjectPtr project;
std::vector<FileTaggerConstPtr> fileTaggers;
std::vector<RulePtr> rules;
@@ -97,18 +105,19 @@ struct ProjectResolver::ProjectContext
ResolvedModulePtr dummyModule;
};
-struct ProjectResolver::ProductContext
+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;
- ProjectResolver::FileLocations sourceArtifactLocations;
+ FileLocations sourceArtifactLocations;
GroupConstPtr currentGroup;
};
-struct ProjectResolver::ModuleContext
+struct ModuleContext
{
ResolvedModulePtr module;
JobLimits jobLimits;
@@ -116,24 +125,166 @@ struct ProjectResolver::ModuleContext
class CancelException { };
+class ProjectResolver::Private
+{
+public:
+ Private(ScriptEngine *engine, Logger &&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();
+
+ 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);
+
+ 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;
+};
+
-ProjectResolver::ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult,
- SetupProjectParameters setupParameters, Logger &logger)
- : m_evaluator(evaluator)
- , m_logger(logger)
- , m_engine(m_evaluator->engine())
- , m_progressObserver(nullptr)
- , m_setupParams(std::move(setupParameters))
- , m_loadResult(std::move(loadResult))
+ProjectResolver::ProjectResolver(ScriptEngine *engine, Logger logger)
+ : d(makePimpl<Private>(engine, std::move(logger)))
{
- QBS_CHECK(FileInfo::isAbsolute(m_setupParams.buildRoot()));
+ d->logger.storeWarnings();
}
ProjectResolver::~ProjectResolver() = default;
void ProjectResolver::setProgressObserver(ProgressObserver *observer)
{
- m_progressObserver = observer;
+ d->progressObserver = observer;
+}
+
+void ProjectResolver::setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes)
+{
+ d->oldProjectProbes = oldProbes;
+}
+
+void ProjectResolver::setOldProductProbes(
+ const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes)
+{
+ d->oldProductProbes = oldProbes;
+}
+
+void ProjectResolver::setLastResolveTime(const FileTime &time)
+{
+ d->lastResolveTime = time;
+}
+
+void ProjectResolver::setStoredProfiles(const QVariantMap &profiles)
+{
+ d->storedProfiles = profiles;
+}
+
+void ProjectResolver::setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo)
+{
+ d->storedModuleProviderInfo = providerInfo;
}
static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project)
@@ -155,33 +306,68 @@ static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project
}
}
-TopLevelProjectPtr ProjectResolver::resolve()
+TopLevelProjectPtr ProjectResolver::resolve(const SetupProjectParameters &parameters)
{
- TimedActivityLogger projectResolverTimer(m_logger, Tr::tr("ProjectResolver"),
- m_setupParams.logElapsedTime());
- qCDebug(lcProjectResolver) << "resolving" << m_loadResult.root->file()->filePath();
+ d->setupParams = parameters;
+ d->finalizeProjectParameters();
+ QBS_CHECK(FileInfo::isAbsolute(d->setupParams.buildRoot()));
+
+ qCDebug(lcProjectResolver) << "resolving" << d->setupParams.projectFilePath();
- m_productContext = nullptr;
- m_moduleContext = nullptr;
- m_elapsedTimeModPropEval = m_elapsedTimeAllPropEval = m_elapsedTimeGroups = 0;
+ d->engine->setEnvironment(d->setupParams.adjustedEnvironment());
+ d->engine->checkAndClearException({});
+ d->engine->clearImportsCache();
+ d->engine->clearRequestedProperties();
+ d->engine->enableProfiling(d->setupParams.logElapsedTime());
+ d->logger.clearWarnings();
+ EvalContextSwitcher evalContextSwitcher(d->engine, EvalContext::PropertyEvaluation);
+
+ // At this point, we cannot set a sensible total effort, because we know nothing about
+ // the project yet. That's why we use a placeholder here, so the user at least
+ // sees that an operation is starting. The real total effort will be set later when
+ // we have enough information.
+ if (d->progressObserver) {
+ d->progressObserver->initialize(Tr::tr("Resolving project for configuration %1")
+ .arg(TopLevelProject::deriveId(d->setupParams.finalBuildConfigurationTree())), 1);
+ d->progressObserver->setScriptEngine(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 {
- tlp = resolveTopLevelProject();
- printProfilingInfo();
+ tlp = d->resolveTopLevelProject();
} catch (const CancelException &) {
throw ErrorInfo(Tr::tr("Project resolving canceled for configuration '%1'.")
- .arg(TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree())));
+ .arg(TopLevelProject::deriveId(
+ d->setupParams.finalBuildConfigurationTree())));
+ }
+ tlp->lastStartResolveTime = resolveTime;
+ tlp->lastEndResolveTime = FileTime::currentTime();
+
+ // E.g. if the top-level project is disabled.
+ if (d->progressObserver) {
+ d->progressObserver->setFinished();
+ d->printProfilingInfo();
}
return tlp;
}
-void ProjectResolver::checkCancelation() const
+void ProjectResolver::Private::checkCancelation() const
{
- if (m_progressObserver && m_progressObserver->canceled())
+ if (progressObserver && progressObserver->canceled())
throw CancelException();
}
-QString ProjectResolver::verbatimValue(const ValueConstPtr &value, bool *propertyWasSet) const
+QString ProjectResolver::Private::verbatimValue(
+ const ValueConstPtr &value, bool *propertyWasSet) const
{
QString result;
if (value && value->type() == Value::JSSourceValueType) {
@@ -197,12 +383,13 @@ QString ProjectResolver::verbatimValue(const ValueConstPtr &value, bool *propert
return result;
}
-QString ProjectResolver::verbatimValue(Item *item, const QString &name, bool *propertyWasSet) const
+QString ProjectResolver::Private::verbatimValue(Item *item, const QString &name,
+ bool *propertyWasSet) const
{
return verbatimValue(item->property(name), propertyWasSet);
}
-void ProjectResolver::ignoreItem(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::ignoreItem(Item *item, ResolverProjectContext *projectContext)
{
Q_UNUSED(item);
Q_UNUSED(projectContext);
@@ -212,7 +399,7 @@ static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject)
{
Set<QString> subProjectNames;
Set<ResolvedProjectPtr> projectsInNeedOfNameChange;
- for (const ResolvedProjectPtr &p : qAsConst(parentProject->subProjects)) {
+ for (const ResolvedProjectPtr &p : std::as_const(parentProject->subProjects)) {
if (!subProjectNames.insert(p->name).second)
projectsInNeedOfNameChange << p;
makeSubProjectNamesUniqe(p);
@@ -231,37 +418,38 @@ static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject)
}
}
-TopLevelProjectPtr ProjectResolver::resolveTopLevelProject()
+TopLevelProjectPtr ProjectResolver::Private::resolveTopLevelProject()
{
- if (m_progressObserver)
- m_progressObserver->setMaximum(int(m_loadResult.productInfos.size()));
+ if (progressObserver)
+ progressObserver->setMaximum(int(loadResult.productInfos.size()));
TopLevelProjectPtr project = TopLevelProject::create();
- project->buildDirectory = TopLevelProject::deriveBuildDirectory(m_setupParams.buildRoot(),
- TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree()));
- project->buildSystemFiles = m_loadResult.qbsFiles;
- project->profileConfigs = m_loadResult.profileConfigs;
- project->probes = m_loadResult.projectProbes;
- project->moduleProviderInfo = m_loadResult.storedModuleProviderInfo;
- ProjectContext projectContext;
+ 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(m_loadResult.root, &projectContext);
+ resolveProject(loadResult.root, &projectContext);
ErrorInfo accumulatedErrors;
- for (const ErrorInfo &e : m_queuedErrors)
+ for (const ErrorInfo &e : queuedErrors)
appendError(accumulatedErrors, e);
if (accumulatedErrors.hasError())
throw accumulatedErrors;
- project->setBuildConfiguration(m_setupParams.finalBuildConfigurationTree());
- project->overriddenValues = m_setupParams.overriddenValues();
- project->canonicalFilePathResults = m_engine->canonicalFilePathResults();
- project->fileExistsResults = m_engine->fileExistsResults();
- project->directoryEntriesResults = m_engine->directoryEntriesResults();
- project->fileLastModifiedResults = m_engine->fileLastModifiedResults();
- project->environment = m_engine->environment();
- project->buildSystemFiles.unite(m_engine->imports());
+ 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());
makeSubProjectNamesUniqe(project);
- resolveProductDependencies(projectContext);
+ resolveProductDependencies();
collectExportedProductDependencies();
checkForDuplicateProductNames(project);
@@ -278,11 +466,11 @@ TopLevelProjectPtr ProjectResolver::resolveTopLevelProject()
artifact->fileTags += "installable";
}
}
- project->warningsEncountered = m_logger.warnings();
+ project->warningsEncountered = logger.warnings();
return project;
}
-void ProjectResolver::resolveProject(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveProject(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
@@ -297,24 +485,23 @@ void ProjectResolver::resolveProject(Item *item, ProjectContext *projectContext)
<< projectContext->project->location << error.toString();
return;
}
- if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict)
+ if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
throw;
- m_logger.printWarning(error);
+ logger.printWarning(error);
}
}
-void ProjectResolver::resolveProjectFully(Item *item, ProjectResolver::ProjectContext *projectContext)
+void ProjectResolver::Private::resolveProjectFully(Item *item, ResolverProjectContext *projectContext)
{
projectContext->project->enabled = projectContext->project->enabled
- && m_evaluator->boolValue(item, StringConstants::conditionProperty());
- projectContext->project->name = m_evaluator->stringValue(item, StringConstants::nameProperty());
+ && evaluator.boolValue(item, StringConstants::conditionProperty());
+ projectContext->project->name = evaluator.stringValue(item, StringConstants::nameProperty());
if (projectContext->project->name.isEmpty())
projectContext->project->name = FileInfo::baseName(item->location().filePath()); // FIXME: Must also be changed in item?
QVariantMap projectProperties;
if (!projectContext->project->enabled) {
projectProperties.insert(StringConstants::profileProperty(),
- m_evaluator->stringValue(item,
- StringConstants::profileProperty()));
+ evaluator.stringValue(item, StringConstants::profileProperty()));
projectContext->project->setProjectProperties(projectProperties);
return;
}
@@ -328,27 +515,27 @@ void ProjectResolver::resolveProjectFully(Item *item, ProjectResolver::ProjectCo
continue;
const ValueConstPtr v = item->property(it.key());
QBS_ASSERT(v && v->type() != Value::ItemValueType, continue);
- const ScopedJsValue sv(m_engine->context(), m_evaluator->value(item, it.key()));
- projectProperties.insert(it.key(), getJsVariant(m_engine->context(), sv));
+ const ScopedJsValue sv(engine->context(), evaluator.value(item, it.key()));
+ projectProperties.insert(it.key(), getJsVariant(engine->context(), sv));
}
projectContext->project->setProjectProperties(projectProperties);
static const ItemFuncMap mapping = {
- { ItemType::Project, &ProjectResolver::resolveProject },
- { ItemType::SubProject, &ProjectResolver::resolveSubProject },
- { ItemType::Product, &ProjectResolver::resolveProduct },
- { ItemType::Probe, &ProjectResolver::ignoreItem },
- { ItemType::FileTagger, &ProjectResolver::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::resolveJobLimit },
- { ItemType::Rule, &ProjectResolver::resolveRule },
- { ItemType::PropertyOptions, &ProjectResolver::ignoreItem }
+ { 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()) {
try {
callItemFunction(mapping, child, projectContext);
} catch (const ErrorInfo &e) {
- m_queuedErrors.push_back(e);
+ queuedErrors.push_back(e);
}
}
@@ -356,9 +543,9 @@ void ProjectResolver::resolveProjectFully(Item *item, ProjectResolver::ProjectCo
postProcess(product, projectContext);
}
-void ProjectResolver::resolveSubProject(Item *item, ProjectResolver::ProjectContext *projectContext)
+void ProjectResolver::Private::resolveSubProject(Item *item, ResolverProjectContext *projectContext)
{
- ProjectContext subProjectContext = createProjectContext(projectContext);
+ ResolverProjectContext subProjectContext = createProjectContext(projectContext);
Item * const projectItem = item->child(ItemType::Project);
if (projectItem) {
@@ -370,39 +557,16 @@ void ProjectResolver::resolveSubProject(Item *item, ProjectResolver::ProjectCont
subProjectContext.project->enabled = false;
Item * const propertiesItem = item->child(ItemType::PropertiesInSubProject);
if (propertiesItem) {
- subProjectContext.project->name
- = m_evaluator->stringValue(propertiesItem, StringConstants::nameProperty());
+ subProjectContext.project->name = evaluator.stringValue(
+ propertiesItem, StringConstants::nameProperty());
}
}
-class ProjectResolver::ProductContextSwitcher
-{
-public:
- ProductContextSwitcher(ProjectResolver *resolver, ProductContext *newContext,
- ProgressObserver *progressObserver)
- : m_resolver(resolver), m_progressObserver(progressObserver)
- {
- QBS_CHECK(!m_resolver->m_productContext);
- m_resolver->m_productContext = newContext;
- }
-
- ~ProductContextSwitcher()
- {
- if (m_progressObserver)
- m_progressObserver->incrementProgressValue();
- m_resolver->m_productContext = nullptr;
- }
-
-private:
- ProjectResolver * const m_resolver;
- ProgressObserver * const m_progressObserver;
-};
-
-void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveProduct(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
- m_evaluator->clearPropertyDependencies();
- ProductContext productContext;
+ evaluator.clearPropertyDependencies();
+ ResolverProductContext productContext;
productContext.item = item;
ResolvedProductPtr product = ResolvedProduct::create();
product->enabled = projectContext->project->enabled;
@@ -410,10 +574,49 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext)
product->project = projectContext->project;
productContext.product = product;
product->location = item->location();
- ProductContextSwitcher contextSwitcher(this, &productContext, m_progressObserver);
+ 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();
+ };
+
+ // 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);
- } catch (const ErrorInfo &e) {
+ 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:");
@@ -423,52 +626,40 @@ void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext)
qCDebug(lcProjectResolver) << fullError.toString();
return;
}
- if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict)
+ if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
throw fullError;
- m_logger.printWarning(fullError);
- m_logger.printWarning(ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.")
- .arg(product->name), item->location()));
+ 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::resolveProductFully(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveProductFully(Item *item, ResolverProjectContext *projectContext)
{
- const ResolvedProductPtr product = m_productContext->product;
- m_productItemMap.insert(product, item);
+ const ResolvedProductPtr product = productContext->product;
projectContext->project->products.push_back(product);
- product->name = m_evaluator->stringValue(item, StringConstants::nameProperty());
+ product->name = evaluator.stringValue(item, StringConstants::nameProperty());
// product->buildDirectory() isn't valid yet, because the productProperties map is not ready.
- m_productContext->buildDirectory
- = m_evaluator->stringValue(item, StringConstants::buildDirectoryProperty());
+ productContext->buildDirectory
+ = evaluator.stringValue(item, StringConstants::buildDirectoryProperty());
product->multiplexConfigurationId
- = m_evaluator->stringValue(item, StringConstants::multiplexConfigurationIdProperty());
+ = evaluator.stringValue(item, StringConstants::multiplexConfigurationIdProperty());
qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName();
- m_productsByName.insert(product->uniqueName(), product);
+ productsByItem.insert(item, product);
product->enabled = product->enabled
- && m_evaluator->boolValue(item, StringConstants::conditionProperty());
- ModuleLoaderResult::ProductInfo &pi = m_loadResult.productInfos[item];
- if (pi.delayedError.hasError()) {
- ErrorInfo errorInfo;
-
- // First item is "main error", gets prepended again in the catch clause.
- const QList<ErrorItem> &items = pi.delayedError.items();
- for (int i = 1; i < items.size(); ++i)
- errorInfo.append(items.at(i));
-
- pi.delayedError.clear();
- throw errorInfo;
- }
+ && evaluator.boolValue(item, StringConstants::conditionProperty());
+ ProductInfo &pi = loadResult.productInfos[item];
gatherProductTypes(product.get(), item);
- product->targetName = m_evaluator->stringValue(item, StringConstants::targetNameProperty());
- product->sourceDirectory = m_evaluator->stringValue(
+ product->targetName = evaluator.stringValue(item, StringConstants::targetNameProperty());
+ product->sourceDirectory = evaluator.stringValue(
item, StringConstants::sourceDirectoryProperty());
- product->destinationDirectory = m_evaluator->stringValue(
+ product->destinationDirectory = evaluator.stringValue(
item, StringConstants::destinationDirProperty());
if (product->destinationDirectory.isEmpty()) {
- product->destinationDirectory = m_productContext->buildDirectory;
+ product->destinationDirectory = productContext->buildDirectory;
} else {
product->destinationDirectory = FileInfo::resolvePath(
product->topLevelProject()->buildDirectory,
@@ -478,7 +669,7 @@ void ProjectResolver::resolveProductFully(Item *item, ProjectContext *projectCon
createProductConfig(product.get());
product->productProperties.insert(StringConstants::destinationDirProperty(),
product->destinationDirectory);
- ModuleProperties::init(m_evaluator->engine(), m_evaluator->scriptValue(item), product.get());
+ ModuleProperties::init(evaluator.engine(), evaluator.scriptValue(item), product.get());
QList<Item *> subItems = item->children();
const ValuePtr filesProperty = item->property(StringConstants::filesProperty());
@@ -493,65 +684,64 @@ void ProjectResolver::resolveProductFully(Item *item, ProjectContext *projectCon
item->property(StringConstants::excludeFilesProperty()));
fakeGroup->setProperty(StringConstants::overrideTagsProperty(),
VariantValue::falseValue());
- fakeGroup->setupForBuiltinType(m_setupParams.deprecationWarningMode(), m_logger);
+ fakeGroup->setupForBuiltinType(setupParams.deprecationWarningMode(), logger);
subItems.prepend(fakeGroup);
}
static const ItemFuncMap mapping = {
- { ItemType::Depends, &ProjectResolver::ignoreItem },
- { ItemType::Rule, &ProjectResolver::resolveRule },
- { ItemType::FileTagger, &ProjectResolver::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::resolveJobLimit },
- { ItemType::Group, &ProjectResolver::resolveGroup },
- { ItemType::Product, &ProjectResolver::resolveShadowProduct },
- { ItemType::Export, &ProjectResolver::resolveExport },
- { ItemType::Probe, &ProjectResolver::ignoreItem },
- { ItemType::PropertyOptions, &ProjectResolver::ignoreItem }
+ { 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 : qAsConst(subItems))
+ for (Item * const child : std::as_const(subItems))
callItemFunction(mapping, child, projectContext);
- for (const ProjectContext *p = projectContext; p; p = p->parentContext) {
+ for (const ResolverProjectContext *p = projectContext; p; p = p->parentContext) {
JobLimits tempLimits = p->jobLimits;
product->jobLimits = tempLimits.update(product->jobLimits);
}
resolveModules(item, projectContext);
-
- for (const FileTag &t : qAsConst(product->fileTags))
- m_productsByType[t].push_back(product);
}
-void ProjectResolver::resolveModules(const Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveModules(const Item *item, ResolverProjectContext *projectContext)
{
JobLimits jobLimits;
- for (const Item::Module &m : item->modules())
- resolveModule(m.name, m.item, m.isProduct, m.parameters, jobLimits, projectContext);
+ 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 (m_productContext->product->jobLimits.getLimit(moduleJobLimit.pool()) == -1)
- m_productContext->product->jobLimits.setJobLimit(moduleJobLimit);
+ if (productContext->product->jobLimits.getLimit(moduleJobLimit.pool()) == -1)
+ productContext->product->jobLimits.setJobLimit(moduleJobLimit);
}
}
-void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct,
- const QVariantMap &parameters, JobLimits &jobLimits,
- ProjectContext *projectContext)
+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 = m_moduleContext;
- ModuleContext moduleContext;
- moduleContext.module = ResolvedModule::create();
- m_moduleContext = &moduleContext;
+ ModuleContext * const oldModuleContext = moduleContext;
+ ModuleContext newModuleContext;
+ newModuleContext.module = ResolvedModule::create();
+ moduleContext = &newModuleContext;
- const ResolvedModulePtr &module = moduleContext.module;
+ const ResolvedModulePtr &module = newModuleContext.module;
module->name = moduleName.toString();
module->isProduct = isProduct;
- module->product = m_productContext->product.get();
+ module->product = productContext->product.get();
module->setupBuildEnvironmentScript.initialize(
scriptFunctionValue(item, StringConstants::setupBuildEnvironmentProperty()));
module->setupRunEnvironmentScript.initialize(
@@ -562,51 +752,45 @@ void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, b
module->moduleDependencies += m.name.toString();
}
- m_productContext->product->modules.push_back(module);
+ productContext->product->modules.push_back(module);
if (!parameters.empty())
- m_productContext->product->moduleParameters[module] = parameters;
+ productContext->product->moduleParameters[module] = parameters;
static const ItemFuncMap mapping {
- { ItemType::Group, &ProjectResolver::ignoreItem },
- { ItemType::Rule, &ProjectResolver::resolveRule },
- { ItemType::FileTagger, &ProjectResolver::resolveFileTagger },
- { ItemType::JobLimit, &ProjectResolver::resolveJobLimit },
- { ItemType::Scanner, &ProjectResolver::resolveScanner },
- { ItemType::PropertyOptions, &ProjectResolver::ignoreItem },
- { ItemType::Depends, &ProjectResolver::ignoreItem },
- { ItemType::Parameter, &ProjectResolver::ignoreItem },
- { ItemType::Properties, &ProjectResolver::ignoreItem },
- { ItemType::Probe, &ProjectResolver::ignoreItem }
+ { 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 < moduleContext.jobLimits.count(); ++i) {
- const JobLimit &newJobLimit = moduleContext.jobLimits.jobLimitAt(i);
+ 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);
}
- m_moduleContext = oldModuleContext;
+ moduleContext = oldModuleContext; // FIXME: Exception safety
}
-void ProjectResolver::gatherProductTypes(ResolvedProduct *product, Item *item)
+void ProjectResolver::Private::gatherProductTypes(ResolvedProduct *product, Item *item)
{
- product->fileTags = m_evaluator->fileTagsValue(item, StringConstants::typeProperty());
- for (const Item::Module &m : item->modules()) {
- if (m.item->isPresentModule()) {
- product->fileTags += m_evaluator->fileTagsValue(m.item,
- StringConstants::additionalProductTypesProperty());
- }
- }
- item->setProperty(StringConstants::typeProperty(),
- VariantValue::create(sorted(product->fileTags.toStringList())));
+ const VariantValuePtr type = item->variantProperty(StringConstants::typeProperty());
+ if (type)
+ product->fileTags = FileTags::fromStringList(type->value().toStringList());
}
-SourceArtifactPtr ProjectResolver::createSourceArtifact(const ResolvedProductPtr &rproduct,
- const QString &fileName, const GroupPtr &group, bool wildcard,
- const CodeLocation &filesLocation, FileLocations *fileLocations,
- ErrorInfo *errorInfo)
+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));
@@ -638,6 +822,23 @@ SourceArtifactPtr ProjectResolver::createSourceArtifact(const ResolvedProductPtr
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)
{
@@ -654,12 +855,12 @@ static QualifiedIdSet propertiesToEvaluate(std::deque<QualifiedId> initialProps,
return allProperties;
}
-QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group,
- const QVariantMap &currentValues)
+QVariantMap ProjectResolver::Private::resolveAdditionalModuleProperties(
+ const Item *group, const QVariantMap &currentValues)
{
// Step 1: Retrieve the properties directly set in the group
const ModulePropertiesPerGroup &mp = mapValue(
- m_loadResult.productInfos, m_productContext->item).modulePropertiesSetInGroups;
+ loadResult.productInfos, productContext->item).modulePropertiesSetInGroups;
const auto it = mp.find(group);
if (it == mp.end())
return {};
@@ -667,8 +868,8 @@ QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group
// Step 2: Gather all properties that depend on these properties.
const QualifiedIdSet &propsToEval = propertiesToEvaluate(
- rangeTo<std::deque<QualifiedId>>(propsSetInGroup),
- m_evaluator->propertyDependencies());
+ rangeTo<std::deque<QualifiedId>>(propsSetInGroup),
+ evaluator.propertyDependencies());
// Step 3: Evaluate all these properties and replace their values in the map
QVariantMap modulesMap = currentValues;
@@ -678,31 +879,29 @@ QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group
= QualifiedId(fullPropName.mid(0, fullPropName.size() - 1)).toString();
propsPerModule[moduleName] << fullPropName.last();
}
- EvalCacheEnabler cachingEnabler(m_evaluator);
- m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory);
+ 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 : qAsConst(propsForModule))
+ for (const QString &prop : std::as_const(propsForModule))
reusableValues.remove(prop);
modulesMap.insert(fullModName,
evaluateProperties(module.item, module.item, reusableValues, true, true));
}
- m_evaluator->clearPathPropertiesBaseDir();
return modulesMap;
}
-void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveGroup(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
- const bool parentEnabled = m_productContext->currentGroup
- ? m_productContext->currentGroup->enabled
- : m_productContext->product->enabled;
+ const bool parentEnabled = productContext->currentGroup
+ ? productContext->currentGroup->enabled
+ : productContext->product->enabled;
const bool isEnabled = parentEnabled
- && m_evaluator->boolValue(item, StringConstants::conditionProperty());
+ && evaluator.boolValue(item, StringConstants::conditionProperty());
try {
resolveGroupFully(item, projectContext, isEnabled);
} catch (const ErrorInfo &error) {
@@ -711,17 +910,16 @@ void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext)
<< error.toString();
return;
}
- if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict)
+ if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
throw;
- m_logger.printWarning(error);
+ logger.printWarning(error);
}
}
-void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectContext *projectContext,
- bool isEnabled)
+void ProjectResolver::Private::resolveGroupFully(
+ Item *item, ResolverProjectContext *projectContext, bool isEnabled)
{
- AccumulatingTimer groupTimer(m_setupParams.logElapsedTime()
- ? &m_elapsedTimeGroups : nullptr);
+ AccumulatingTimer groupTimer(setupParams.logElapsedTime() ? &elapsedTimeGroups : nullptr);
const auto getGroupPropertyMap = [this, item](const ArtifactProperties *existingProps) {
PropertyMapPtr moduleProperties;
@@ -730,9 +928,9 @@ void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectCont
moduleProperties = existingProps->propertyMap();
if (!moduleProperties) {
newPropertyMapRequired = true;
- moduleProperties = m_productContext->currentGroup
- ? m_productContext->currentGroup->properties
- : m_productContext->product->moduleProperties;
+ moduleProperties = productContext->currentGroup
+ ? productContext->currentGroup->properties
+ : productContext->product->moduleProperties;
}
const QVariantMap newModuleProperties
= resolveAdditionalModuleProperties(item, moduleProperties->value());
@@ -744,12 +942,12 @@ void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectCont
return moduleProperties;
};
- QStringList files = m_evaluator->stringListValue(item, StringConstants::filesProperty());
+ QStringList files = evaluator.stringListValue(item, StringConstants::filesProperty());
bool fileTagsSet;
- const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty(),
- &fileTagsSet);
+ const FileTags fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty(),
+ &fileTagsSet);
const QStringList fileTagsFilter
- = m_evaluator->stringListValue(item, StringConstants::fileTagsFilterProperty());
+ = evaluator.stringListValue(item, StringConstants::fileTagsFilterProperty());
if (!fileTagsFilter.empty()) {
if (Q_UNLIKELY(!files.empty()))
throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."),
@@ -758,8 +956,8 @@ void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectCont
if (!isEnabled)
return;
- ProductContext::ArtifactPropertiesInfo &apinfo
- = m_productContext->artifactPropertiesPerFilter[fileTagsFilter];
+ 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) {
@@ -774,7 +972,7 @@ void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectCont
} else {
apinfo.first = ArtifactProperties::create();
apinfo.first->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter));
- m_productContext->product->artifactProperties.push_back(apinfo.first);
+ productContext->product->artifactProperties.push_back(apinfo.first);
}
apinfo.second.push_back(item->location());
apinfo.first->setPropertyMapInternal(getGroupPropertyMap(apinfo.first.get()));
@@ -788,10 +986,10 @@ void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectCont
}
GroupPtr group = ResolvedGroup::create();
bool prefixWasSet = false;
- group->prefix = m_evaluator->stringValue(item, StringConstants::prefixProperty(), QString(),
- &prefixWasSet);
- if (!prefixWasSet && m_productContext->currentGroup)
- group->prefix = m_productContext->currentGroup->prefix;
+ 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);
@@ -800,12 +998,12 @@ void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectCont
group->enabled = isEnabled;
group->properties = getGroupPropertyMap(nullptr);
group->fileTags = fileTags;
- group->overrideTags = m_evaluator->boolValue(item, StringConstants::overrideTagsProperty());
+ group->overrideTags = evaluator.boolValue(item, StringConstants::overrideTagsProperty());
if (group->overrideTags && fileTagsSet) {
if (group->fileTags.empty() )
group->fileTags.insert(unknownFileTag());
- } else if (m_productContext->currentGroup) {
- group->fileTags.unite(m_productContext->currentGroup->fileTags);
+ } else if (productContext->currentGroup) {
+ group->fileTags.unite(productContext->currentGroup->fileTags);
}
const CodeLocation filesLocation = item->property(StringConstants::filesProperty())->location();
@@ -818,59 +1016,59 @@ void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectCont
group->wildcards = std::make_unique<SourceWildCards>();
SourceWildCards *wildcards = group->wildcards.get();
wildcards->group = group.get();
- wildcards->excludePatterns = m_evaluator->stringListValue(
- item, StringConstants::excludeFilesProperty());
+ 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(m_productContext->product, fileName, group, true, filesLocation,
- &m_productContext->sourceArtifactLocations, &fileError);
+ createSourceArtifact(productContext->product, fileName, group, true, filesLocation,
+ &productContext->sourceArtifactLocations, &fileError);
}
- for (const QString &fileName : qAsConst(files)) {
- createSourceArtifact(m_productContext->product, fileName, group, false, filesLocation,
- &m_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 (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict)
+ if (setupParams.productErrorMode() == ErrorHandlingMode::Strict)
throw ErrorInfo(fileError);
- m_logger.printWarning(fileError);
+ logger.printWarning(fileError);
} else {
qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString();
}
}
- group->name = m_evaluator->stringValue(item, StringConstants::nameProperty());
+ group->name = evaluator.stringValue(item, StringConstants::nameProperty());
if (group->name.isEmpty())
- group->name = Tr::tr("Group %1").arg(m_productContext->product->groups.size());
- m_productContext->product->groups.push_back(group);
+ group->name = Tr::tr("Group %1").arg(productContext->product->groups.size());
+ productContext->product->groups.push_back(group);
class GroupContextSwitcher {
public:
- GroupContextSwitcher(ProductContext &context, const GroupConstPtr &newGroup)
+ GroupContextSwitcher(ResolverProductContext &context, const GroupConstPtr &newGroup)
: m_context(context), m_oldGroup(context.currentGroup) {
m_context.currentGroup = newGroup;
}
~GroupContextSwitcher() { m_context.currentGroup = m_oldGroup; }
private:
- ProductContext &m_context;
+ ResolverProductContext &m_context;
const GroupConstPtr m_oldGroup;
};
- GroupContextSwitcher groupSwitcher(*m_productContext, group);
+ GroupContextSwitcher groupSwitcher(*productContext, group);
for (Item * const childItem : item->children())
resolveGroup(childItem, projectContext);
}
-void ProjectResolver::adaptExportedPropertyValues(const Item *shadowProductItem)
+void ProjectResolver::Private::adaptExportedPropertyValues(const Item *shadowProductItem)
{
- ExportedModule &m = m_productContext->product->exportedModule;
+ ExportedModule &m = productContext->product->exportedModule;
const QVariantList prefixList = m.propertyValues.take(
StringConstants::prefixMappingProperty()).toList();
- const QString shadowProductName = m_evaluator->stringValue(
+ const QString shadowProductName = evaluator.stringValue(
shadowProductItem, StringConstants::nameProperty());
- const QString shadowProductBuildDir = m_evaluator->stringValue(
+ const QString shadowProductBuildDir = evaluator.stringValue(
shadowProductItem, StringConstants::buildDirectoryProperty());
QVariantMap prefixMap;
for (const QVariant &v : prefixList) {
@@ -929,46 +1127,36 @@ void ProjectResolver::adaptExportedPropertyValues(const Item *shadowProductItem)
}
}
-void ProjectResolver::collectExportedProductDependencies()
+void ProjectResolver::Private::collectExportedProductDependencies()
{
ResolvedProductPtr dummyProduct = ResolvedProduct::create();
dummyProduct->enabled = false;
- for (const auto &exportingProductInfo : qAsConst(m_productExportInfo)) {
+ for (const auto &exportingProductInfo : std::as_const(productExportInfo)) {
const ResolvedProductPtr exportingProduct = exportingProductInfo.first;
if (!exportingProduct->enabled)
continue;
Item * const importingProductItem = exportingProductInfo.second;
- std::vector<QString> directDepNames;
+
+ std::vector<std::pair<ResolvedProductPtr, QVariantMap>> directDeps;
for (const Item::Module &m : importingProductItem->modules()) {
- if (m.name.toString() == exportingProduct->name) {
- for (const Item::Module &dep : m.item->modules()) {
- if (dep.isProduct)
- directDepNames.push_back(dep.name.toString());
+ 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);
}
- break;
}
}
- const ModuleLoaderResult::ProductInfo &importingProductInfo
- = mapValue(m_loadResult.productInfos, importingProductItem);
- const ProductDependencyInfos &depInfos
- = getProductDependencies(dummyProduct, importingProductInfo);
- for (const auto &dep : depInfos.dependencies) {
- if (dep.product == exportingProduct)
- continue;
-
- // Filter out indirect dependencies.
- // TODO: Depends items using "profile" or "productTypes" will not work.
- if (!contains(directDepNames, dep.product->name))
- continue;
-
+ for (const auto &dep : directDeps) {
if (!contains(exportingProduct->exportedModule.productDependencies,
- dep.product->uniqueName())) {
+ dep.first->uniqueName())) {
exportingProduct->exportedModule.productDependencies.push_back(
- dep.product->uniqueName());
+ dep.first->uniqueName());
}
- if (!dep.parameters.isEmpty()) {
- exportingProduct->exportedModule.dependencyParameters.insert(dep.product,
- dep.parameters);
+ if (!dep.second.isEmpty()) {
+ exportingProduct->exportedModule.dependencyParameters.insert(dep.first,
+ dep.second);
}
}
auto &productDeps = exportingProduct->exportedModule.productDependencies;
@@ -976,12 +1164,12 @@ void ProjectResolver::collectExportedProductDependencies()
}
}
-void ProjectResolver::resolveShadowProduct(Item *item, ProjectResolver::ProjectContext *)
+void ProjectResolver::Private::resolveShadowProduct(Item *item, ResolverProjectContext *)
{
- if (!m_productContext->product->enabled)
+ if (!productContext->product->enabled)
return;
for (const auto &m : item->modules()) {
- if (m.name.toString() != m_productContext->product->name)
+ if (m.name.toString() != productContext->product->name)
continue;
collectPropertiesForExportItem(m.item);
for (const auto &dep : m.item->modules())
@@ -991,11 +1179,11 @@ void ProjectResolver::resolveShadowProduct(Item *item, ProjectResolver::ProjectC
try {
adaptExportedPropertyValues(item);
} catch (const ErrorInfo &) {}
- m_productExportInfo.emplace_back(m_productContext->product, item);
+ productExportInfo.emplace_back(productContext->product, item);
}
-void ProjectResolver::setupExportedProperties(const Item *item, const QString &namePrefix,
- std::vector<ExportedProperty> &properties)
+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) {
@@ -1035,7 +1223,7 @@ void ProjectResolver::setupExportedProperties(const Item *item, const QString &n
}
// Do not add built-in properties that were left at their default value.
- if (!exportedProperty.isBuiltin || m_evaluator->isNonDefaultValue(item, it.key()))
+ if (!exportedProperty.isBuiltin || evaluator.isNonDefaultValue(item, it.key()))
properties.push_back(exportedProperty);
}
@@ -1097,9 +1285,9 @@ static QString getLineAtLocation(const CodeLocation &loc, const QString &content
return content.mid(pos, eolPos - pos);
}
-void ProjectResolver::resolveExport(Item *exportItem, ProjectContext *)
+void ProjectResolver::Private::resolveExport(Item *exportItem, ResolverProjectContext *)
{
- ExportedModule &exportedModule = m_productContext->product->exportedModule;
+ 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;
@@ -1125,8 +1313,8 @@ void ProjectResolver::resolveExport(Item *exportItem, ProjectContext *)
}
// TODO: This probably wouldn't be necessary if we had item serialization.
-std::unique_ptr<ExportedItem> ProjectResolver::resolveExportChild(const Item *item,
- const ExportedModule &module)
+std::unique_ptr<ExportedItem> ProjectResolver::Private::resolveExportChild(
+ const Item *item, const ExportedModule &module)
{
std::unique_ptr<ExportedItem> exportedItem(new ExportedItem);
@@ -1142,11 +1330,11 @@ std::unique_ptr<ExportedItem> ProjectResolver::resolveExportChild(const Item *it
return exportedItem;
}
-QString ProjectResolver::sourceCodeAsFunction(const JSSourceValueConstPtr &value,
- const PropertyDeclaration &decl) const
+QString ProjectResolver::Private::sourceCodeAsFunction(const JSSourceValueConstPtr &value,
+ const PropertyDeclaration &decl) const
{
- QString &scriptFunction = m_scriptFunctions[std::make_pair(value->sourceCode(),
- decl.functionArgumentNames())];
+ QString &scriptFunction = scriptFunctions[std::make_pair(value->sourceCode(),
+ decl.functionArgumentNames())];
if (!scriptFunction.isNull())
return scriptFunction;
const QString args = decl.functionArgumentNames().join(QLatin1Char(','));
@@ -1164,19 +1352,20 @@ QString ProjectResolver::sourceCodeAsFunction(const JSSourceValueConstPtr &value
return scriptFunction;
}
-QString ProjectResolver::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const
+QString ProjectResolver::Private::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const
{
- QString &code = m_sourceCode[value->sourceCode()];
+ QString &code = sourceCode[value->sourceCode()];
if (!code.isNull())
return code;
code = value->sourceCodeForEvaluation();
return code;
}
-ScriptFunctionPtr ProjectResolver::scriptFunctionValue(Item *item, const QString &name) const
+ScriptFunctionPtr ProjectResolver::Private::scriptFunctionValue(
+ Item *item, const QString &name) const
{
JSSourceValuePtr value = item->sourceProperty(name);
- ScriptFunctionPtr &script = m_scriptFunctionMap[value ? value->location() : CodeLocation()];
+ ScriptFunctionPtr &script = scriptFunctionMap[value ? value->location() : CodeLocation()];
if (!script.get()) {
script = ScriptFunction::create();
const PropertyDeclaration decl = item->propertyDeclaration(name);
@@ -1187,19 +1376,20 @@ ScriptFunctionPtr ProjectResolver::scriptFunctionValue(Item *item, const QString
return script;
}
-ResolvedFileContextPtr ProjectResolver::resolvedFileContext(const FileContextConstPtr &ctx) const
+ResolvedFileContextPtr ProjectResolver::Private::resolvedFileContext(
+ const FileContextConstPtr &ctx) const
{
- ResolvedFileContextPtr &result = m_fileContextMap[ctx];
+ ResolvedFileContextPtr &result = fileContextMap[ctx];
if (!result)
result = ResolvedFileContext::create(*ctx);
return result;
}
-void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveRule(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
- if (!m_evaluator->boolValue(item, StringConstants::conditionProperty()))
+ if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
return;
RulePtr rule = Rule::create();
@@ -1215,13 +1405,12 @@ void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext)
resolveRuleArtifact(rule, child);
}
- rule->name = m_evaluator->stringValue(item, StringConstants::nameProperty());
+ rule->name = evaluator.stringValue(item, StringConstants::nameProperty());
rule->prepareScript.initialize(
scriptFunctionValue(item, StringConstants::prepareProperty()));
rule->outputArtifactsScript.initialize(
scriptFunctionValue(item, StringConstants::outputArtifactsProperty()));
- rule->outputFileTags = m_evaluator->fileTagsValue(
- item, StringConstants::outputFileTagsProperty());
+ 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 "
@@ -1232,29 +1421,29 @@ void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext)
throw ErrorInfo(Tr::tr("A rule needs to have Artifact items or a non-empty "
"outputFileTags property."), item->location());
}
- rule->multiplex = m_evaluator->boolValue(item, StringConstants::multiplexProperty());
- rule->alwaysRun = m_evaluator->boolValue(item, StringConstants::alwaysRunProperty());
- rule->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty());
+ rule->multiplex = evaluator.boolValue(item, StringConstants::multiplexProperty());
+ rule->alwaysRun = evaluator.boolValue(item, StringConstants::alwaysRunProperty());
+ rule->inputs = evaluator.fileTagsValue(item, StringConstants::inputsProperty());
rule->inputsFromDependencies
- = m_evaluator->fileTagsValue(item, StringConstants::inputsFromDependenciesProperty());
+ = evaluator.fileTagsValue(item, StringConstants::inputsFromDependenciesProperty());
bool requiresInputsSet = false;
- rule->requiresInputs = m_evaluator->boolValue(item, StringConstants::requiresInputsProperty(),
- &requiresInputsSet);
+ rule->requiresInputs = evaluator.boolValue(item, StringConstants::requiresInputsProperty(),
+ &requiresInputsSet);
if (!requiresInputsSet)
rule->requiresInputs = rule->declaresInputs();
rule->auxiliaryInputs
- = m_evaluator->fileTagsValue(item, StringConstants::auxiliaryInputsProperty());
+ = evaluator.fileTagsValue(item, StringConstants::auxiliaryInputsProperty());
rule->excludedInputs
- = m_evaluator->fileTagsValue(item, StringConstants::excludedInputsProperty());
+ = evaluator.fileTagsValue(item, StringConstants::excludedInputsProperty());
if (rule->excludedInputs.empty()) {
- rule->excludedInputs = m_evaluator->fileTagsValue(
+ rule->excludedInputs = evaluator.fileTagsValue(
item, StringConstants::excludedAuxiliaryInputsProperty());
}
rule->explicitlyDependsOn
- = m_evaluator->fileTagsValue(item, StringConstants::explicitlyDependsOnProperty());
- rule->explicitlyDependsOnFromDependencies = m_evaluator->fileTagsValue(
+ = evaluator.fileTagsValue(item, StringConstants::explicitlyDependsOnProperty());
+ rule->explicitlyDependsOnFromDependencies = evaluator.fileTagsValue(
item, StringConstants::explicitlyDependsOnFromDependenciesProperty());
- rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule;
+ 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());
@@ -1267,15 +1456,15 @@ void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext)
throw ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule "
"does not declare any input tags."), item->location());
}
- if (m_productContext) {
- rule->product = m_productContext->product.get();
- m_productContext->product->rules.push_back(rule);
+ if (productContext) {
+ rule->product = productContext->product.get();
+ productContext->product->rules.push_back(rule);
} else {
projectContext->rules.push_back(rule);
}
}
-void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item)
+void ProjectResolver::Private::resolveRuleArtifact(const RulePtr &rule, Item *item)
{
RuleArtifactPtr artifact = RuleArtifact::create();
rule->artifacts.push_back(artifact);
@@ -1285,9 +1474,9 @@ void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item)
artifact->filePathLocation = sourceProperty->location();
artifact->filePath = verbatimValue(item, StringConstants::filePathProperty());
- artifact->fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty());
- artifact->alwaysUpdated = m_evaluator->boolValue(item,
- StringConstants::alwaysUpdatedProperty());
+ artifact->fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty());
+ artifact->alwaysUpdated = evaluator.boolValue(item,
+ StringConstants::alwaysUpdatedProperty());
QualifiedIdSet seenBindings;
for (Item *obj = item; obj; obj = obj->prototype()) {
@@ -1303,10 +1492,9 @@ void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item)
}
}
-void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact,
- Item *item,
- const QStringList &namePrefix,
- QualifiedIdSet *seenBindings)
+void ProjectResolver::Private::resolveRuleArtifactBinding(
+ const RuleArtifactPtr &ruleArtifact, Item *item, const QStringList &namePrefix,
+ QualifiedIdSet *seenBindings)
{
for (QMap<QString, ValuePtr>::const_iterator it = item->properties().constBegin();
it != item->properties().constEnd(); ++it)
@@ -1332,20 +1520,20 @@ void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArti
}
}
-void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectContext)
+void ProjectResolver::Private::resolveFileTagger(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
- if (!m_evaluator->boolValue(item, StringConstants::conditionProperty()))
+ if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
return;
- std::vector<FileTaggerConstPtr> &fileTaggers = m_productContext
- ? m_productContext->product->fileTaggers
+ std::vector<FileTaggerConstPtr> &fileTaggers = productContext
+ ? productContext->product->fileTaggers
: projectContext->fileTaggers;
- const QStringList patterns = m_evaluator->stringListValue(item,
- StringConstants::patternsProperty());
+ 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 = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty());
+ const FileTags fileTags = evaluator.fileTagsValue(item, StringConstants::fileTagsProperty());
if (fileTags.empty())
throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location());
@@ -1354,21 +1542,21 @@ void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectConte
throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location());
}
- const int priority = m_evaluator->intValue(item, StringConstants::priorityProperty());
+ const int priority = evaluator.intValue(item, StringConstants::priorityProperty());
fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority));
}
-void ProjectResolver::resolveJobLimit(Item *item, ProjectResolver::ProjectContext *projectContext)
+void ProjectResolver::Private::resolveJobLimit(Item *item, ResolverProjectContext *projectContext)
{
- if (!m_evaluator->boolValue(item, StringConstants::conditionProperty()))
+ if (!evaluator.boolValue(item, StringConstants::conditionProperty()))
return;
- const QString jobPool = m_evaluator->stringValue(item, StringConstants::jobPoolProperty());
+ 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 = m_evaluator->intValue(item, StringConstants::jobCountProperty(), -1,
- &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());
@@ -1377,9 +1565,9 @@ void ProjectResolver::resolveJobLimit(Item *item, ProjectResolver::ProjectContex
throw ErrorInfo(Tr::tr("A JobLimit item must have a non-negative '%1' property.")
.arg(StringConstants::jobCountProperty()), item->location());
}
- JobLimits &jobLimits = m_moduleContext
- ? m_moduleContext->jobLimits
- : m_productContext ? m_productContext->product->jobLimits
+ JobLimits &jobLimits = moduleContext
+ ? moduleContext->jobLimits
+ : productContext ? productContext->product->jobLimits
: projectContext->jobLimits;
JobLimit jobLimit(jobPool, jobCount);
const int oldLimit = jobLimits.getLimit(jobPool);
@@ -1387,84 +1575,26 @@ void ProjectResolver::resolveJobLimit(Item *item, ProjectResolver::ProjectContex
jobLimits.setJobLimit(jobLimit);
}
-void ProjectResolver::resolveScanner(Item *item, ProjectResolver::ProjectContext *projectContext)
+void ProjectResolver::Private::resolveScanner(Item *item, ResolverProjectContext *projectContext)
{
checkCancelation();
- if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) {
+ if (!evaluator.boolValue(item, StringConstants::conditionProperty())) {
qCDebug(lcProjectResolver) << "scanner condition is false";
return;
}
ResolvedScannerPtr scanner = ResolvedScanner::create();
- scanner->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule;
- scanner->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty());
- scanner->recursive = m_evaluator->boolValue(item, StringConstants::recursiveProperty());
+ scanner->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()));
- m_productContext->product->scanners.push_back(scanner);
-}
-
-ProjectResolver::ProductDependencyInfos ProjectResolver::getProductDependencies(
- const ResolvedProductConstPtr &product, const ModuleLoaderResult::ProductInfo &productInfo)
-{
- ProductDependencyInfos result;
- result.dependencies.reserve(productInfo.usedProducts.size());
- for (const auto &dependency : productInfo.usedProducts) {
- QBS_CHECK(!dependency.name.isEmpty());
- if (dependency.profile == StringConstants::star()) {
- for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) {
- if (p->name != dependency.name || p == product || !p->enabled
- || (dependency.limitToSubProject && !product->isInParentProject(p))) {
- continue;
- }
- result.dependencies.emplace_back(p, dependency.parameters);
- }
- } else {
- ResolvedProductPtr usedProduct = m_productsByName.value(dependency.uniqueName());
- const QString depDisplayName = ResolvedProduct::fullDisplayName(dependency.name,
- dependency.multiplexConfigurationId);
- if (!usedProduct) {
- throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist.")
- .arg(product->fullDisplayName(), depDisplayName),
- product->location);
- }
- if (!dependency.profile.isEmpty() && usedProduct->profile() != dependency.profile) {
- usedProduct.reset();
- for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) {
- if (p->name == dependency.name && p->profile() == dependency.profile) {
- usedProduct = p;
- break;
- }
- }
- if (!usedProduct) {
- throw ErrorInfo(Tr::tr("Product '%1' depends on '%2', which does not exist "
- "for the requested profile '%3'.")
- .arg(product->fullDisplayName(), depDisplayName,
- dependency.profile),
- product->location);
- }
- }
- if (!usedProduct->enabled) {
- if (!dependency.isRequired)
- continue;
- ErrorInfo e;
- e.append(Tr::tr("Product '%1' depends on '%2',")
- .arg(product->name, usedProduct->name), product->location);
- e.append(Tr::tr("but product '%1' is disabled.").arg(usedProduct->name),
- usedProduct->location);
- if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict)
- throw e;
- result.hasDisabledDependency = true;
- }
- result.dependencies.emplace_back(usedProduct, dependency.parameters);
- }
- }
- return result;
+ productContext->product->scanners.push_back(scanner);
}
-void ProjectResolver::matchArtifactProperties(const ResolvedProductPtr &product,
+void ProjectResolver::Private::matchArtifactProperties(const ResolvedProductPtr &product,
const std::vector<SourceArtifactPtr> &artifacts)
{
for (const SourceArtifactPtr &artifact : artifacts) {
@@ -1477,43 +1607,57 @@ void ProjectResolver::matchArtifactProperties(const ResolvedProductPtr &product,
}
}
-void ProjectResolver::printProfilingInfo()
+void ProjectResolver::Private::printProfilingInfo()
{
- if (!m_setupParams.logElapsedTime())
+ if (!setupParams.logElapsedTime())
return;
- m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("All property evaluation took %1.")
- .arg(elapsedTimeString(m_elapsedTimeAllPropEval));
- m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Module property evaluation took %1.")
- .arg(elapsedTimeString(m_elapsedTimeModPropEval));
- m_logger.qbsLog(LoggerInfo, true) << "\t"
- << Tr::tr("Resolving groups (without module property "
- "evaluation) took %1.")
- .arg(elapsedTimeString(m_elapsedTimeGroups));
+ 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(Item * item, Item *newScope) : m_item(item), m_oldScope(item->scope())
+ TempScopeSetter(const ValuePtr &value, Item *newScope) : m_value(value), m_oldScope(value->scope())
{
- item->setScope(newScope);
+ value->setScope(newScope, {});
}
- ~TempScopeSetter() { m_item->setScope(m_oldScope); }
+ ~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:
- Item * const m_item;
- Item * const m_oldScope;
+ ValuePtr m_value;
+ Item *m_oldScope;
};
-void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance)
+void ProjectResolver::Private::collectPropertiesForExportItem(Item *productModuleInstance)
{
if (!productModuleInstance->isPresentModule())
return;
Item * const exportItem = productModuleInstance->prototype();
- QBS_CHECK(exportItem && exportItem->type() == ItemType::Export);
- TempScopeSetter tempScopeSetter(exportItem, productModuleInstance->scope());
+ QBS_CHECK(exportItem);
+ QBS_CHECK(exportItem->type() == ItemType::Export);
const ItemDeclaration::Properties exportDecls = BuiltinDeclarations::instance()
.declarationsForType(ItemType::Export).properties();
- ExportedModule &exportedModule = m_productContext->product->exportedModule;
+ ExportedModule &exportedModule = productContext->product->exportedModule;
const auto &props = exportItem->properties();
for (auto it = props.begin(); it != props.end(); ++it) {
const auto match
@@ -1526,6 +1670,7 @@ void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance
collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance,
exportedModule.modulePropertyValues);
} else {
+ TempScopeSetter tss(it.value(), productModuleInstance);
evaluateProperty(exportItem, it.key(), it.value(), exportedModule.propertyValues,
false);
}
@@ -1533,12 +1678,12 @@ void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance
}
// Collects module properties assigned to in other (higher-level) modules.
-void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module &module)
+void ProjectResolver::Private::collectPropertiesForModuleInExportItem(const Item::Module &module)
{
if (!module.item->isPresentModule())
return;
- ExportedModule &exportedModule = m_productContext->product->exportedModule;
- if (module.isProduct || module.name.first() == StringConstants::qbsModule())
+ 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;
@@ -1551,7 +1696,6 @@ void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module
modulePrototype = modulePrototype->prototype();
if (!modulePrototype) // Can happen for broken products in relaxed mode.
return;
- TempScopeSetter tempScopeSetter(modulePrototype, module.item->scope());
const Item::PropertyMap &props = modulePrototype->properties();
ExportedModuleDependency dep;
dep.name = module.name.toString();
@@ -1565,112 +1709,30 @@ void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module
collectPropertiesForModuleInExportItem(dep);
}
-static bool hasDependencyCycle(Set<ResolvedProduct *> *checked,
- Set<ResolvedProduct *> *branch,
- const ResolvedProductPtr &product,
- ErrorInfo *error)
-{
- if (branch->contains(product.get()))
- return true;
- if (checked->contains(product.get()))
- return false;
- checked->insert(product.get());
- branch->insert(product.get());
- for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) {
- if (hasDependencyCycle(checked, branch, dep, error)) {
- error->prepend(dep->name, dep->location);
- return true;
- }
- }
- branch->remove(product.get());
- return false;
-}
-
-using DependencyMap = QHash<ResolvedProduct *, Set<ResolvedProduct *>>;
-void gatherDependencies(ResolvedProduct *product, DependencyMap &dependencies)
+void ProjectResolver::Private::resolveProductDependencies()
{
- if (dependencies.contains(product))
- return;
- // Hold locally because the QHash references aren't stable in Qt6.
- Set<ResolvedProduct *> productDeps = dependencies[product];
- for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) {
- productDeps << dep.get();
- gatherDependencies(dep.get(), dependencies);
- productDeps += dependencies.value(dep.get());
- }
- // Now that we gathered the dependencies, put them in the map.
- dependencies[product] = std::move(productDeps);
-}
-
-
-
-static DependencyMap allDependencies(const std::vector<ResolvedProductPtr> &products)
-{
- DependencyMap dependencies;
- for (const ResolvedProductPtr &product : products)
- gatherDependencies(product.get(), dependencies);
- return dependencies;
-}
-
-void ProjectResolver::resolveProductDependencies(const ProjectContext &projectContext)
-{
- // Resolve all inter-product dependencies.
- const std::vector<ResolvedProductPtr> allProducts = projectContext.project->allProducts();
- bool disabledDependency = false;
- for (const ResolvedProductPtr &rproduct : allProducts) {
- if (!rproduct->enabled)
- continue;
- Item *productItem = m_productItemMap.value(rproduct);
- const ModuleLoaderResult::ProductInfo &productInfo
- = mapValue(m_loadResult.productInfos, productItem);
- const ProductDependencyInfos &depInfos = getProductDependencies(rproduct, productInfo);
- if (depInfos.hasDisabledDependency)
- disabledDependency = true;
- for (const auto &dep : depInfos.dependencies) {
- if (!contains(rproduct->dependencies, dep.product))
- rproduct->dependencies.push_back(dep.product);
- if (!dep.parameters.empty())
- rproduct->dependencyParameters.insert(dep.product, dep.parameters);
- }
- }
-
- // Check for cyclic dependencies.
- Set<ResolvedProduct *> checked;
- for (const ResolvedProductPtr &rproduct : allProducts) {
- Set<ResolvedProduct *> branch;
- ErrorInfo error;
- if (hasDependencyCycle(&checked, &branch, rproduct, &error)) {
- error.prepend(rproduct->name, rproduct->location);
- error.prepend(Tr::tr("Cyclic dependencies detected."));
- throw error;
- }
- }
-
- // Mark all products as disabled that have a disabled dependency.
- if (disabledDependency && m_setupParams.productErrorMode() == ErrorHandlingMode::Relaxed) {
- const DependencyMap allDeps = allDependencies(allProducts);
- DependencyMap allDepsReversed;
- for (auto it = allDeps.constBegin(); it != allDeps.constEnd(); ++it) {
- for (ResolvedProduct *dep : qAsConst(it.value()))
- allDepsReversed[dep] << it.key();
- }
- for (auto it = allDepsReversed.constBegin(); it != allDepsReversed.constEnd(); ++it) {
- if (it.key()->enabled)
+ 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;
- for (ResolvedProduct * const dependingProduct : qAsConst(it.value())) {
- if (dependingProduct->enabled) {
- m_logger.qbsWarning() << Tr::tr("Disabling product '%1', because it depends on "
- "disabled product '%2'.")
- .arg(dependingProduct->name, it.key()->name);
- dependingProduct->enabled = false;
- }
- }
+ 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::postProcess(const ResolvedProductPtr &product,
- ProjectContext *projectContext) const
+void ProjectResolver::Private::postProcess(const ResolvedProductPtr &product,
+ ResolverProjectContext *projectContext) const
{
product->fileTaggers << projectContext->fileTaggers;
std::sort(std::begin(product->fileTaggers), std::end(product->fileTaggers),
@@ -1684,13 +1746,13 @@ void ProjectResolver::postProcess(const ResolvedProductPtr &product,
}
}
-void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const
+void ProjectResolver::Private::applyFileTaggers(const ResolvedProductPtr &product) const
{
for (const SourceArtifactPtr &artifact : product->allEnabledFiles())
applyFileTaggers(artifact, product);
}
-void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact,
+void ProjectResolver::Private::applyFileTaggers(const SourceArtifactPtr &artifact,
const ResolvedProductConstPtr &product)
{
if (!artifact->overrideFileTags || artifact->fileTags.empty()) {
@@ -1704,10 +1766,10 @@ void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact,
}
}
-QVariantMap ProjectResolver::evaluateModuleValues(Item *item, bool lookupPrototype)
+QVariantMap ProjectResolver::Private::evaluateModuleValues(Item *item, bool lookupPrototype)
{
- AccumulatingTimer modPropEvalTimer(m_setupParams.logElapsedTime()
- ? &m_elapsedTimeModPropEval : nullptr);
+ AccumulatingTimer modPropEvalTimer(setupParams.logElapsedTime()
+ ? &elapsedTimeModPropEval : nullptr);
QVariantMap moduleValues;
for (const Item::Module &module : item->modules()) {
if (!module.item->isPresentModule())
@@ -1719,17 +1781,19 @@ QVariantMap ProjectResolver::evaluateModuleValues(Item *item, bool lookupPrototy
return moduleValues;
}
-QVariantMap ProjectResolver::evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors)
+QVariantMap ProjectResolver::Private::evaluateProperties(Item *item, bool lookupPrototype,
+ bool checkErrors)
{
const QVariantMap tmplt;
return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors);
}
-QVariantMap ProjectResolver::evaluateProperties(const Item *item, const Item *propertiesContainer,
- const QVariantMap &tmplt, bool lookupPrototype, bool checkErrors)
+QVariantMap ProjectResolver::Private::evaluateProperties(
+ const Item *item, const Item *propertiesContainer, const QVariantMap &tmplt,
+ bool lookupPrototype, bool checkErrors)
{
- AccumulatingTimer propEvalTimer(m_setupParams.logElapsedTime()
- ? &m_elapsedTimeAllPropEval : nullptr);
+ AccumulatingTimer propEvalTimer(setupParams.logElapsedTime()
+ ? &elapsedTimeAllPropEval : nullptr);
QVariantMap result = tmplt;
for (QMap<QString, ValuePtr>::const_iterator it = propertiesContainer->properties().begin();
it != propertiesContainer->properties().end(); ++it) {
@@ -1741,10 +1805,11 @@ QVariantMap ProjectResolver::evaluateProperties(const Item *item, const Item *pr
: result;
}
-void ProjectResolver::evaluateProperty(const Item *item, const QString &propName,
- const ValuePtr &propValue, QVariantMap &result, bool checkErrors)
+void ProjectResolver::Private::evaluateProperty(
+ const Item *item, const QString &propName, const ValuePtr &propValue, QVariantMap &result,
+ bool checkErrors)
{
- JSContext * const ctx = m_engine->context();
+ JSContext * const ctx = engine->context();
switch (propValue->type()) {
case Value::ItemValueType:
{
@@ -1760,8 +1825,8 @@ void ProjectResolver::evaluateProperty(const Item *item, const QString &propName
if (pd.flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) {
break;
}
- const ScopedJsValue scriptValue(ctx, m_evaluator->property(item, propName));
- if (JsException ex = m_evaluator->engine()->checkAndClearException(propValue->location())) {
+ const ScopedJsValue scriptValue(ctx, evaluator.property(item, propName));
+ if (JsException ex = evaluator.engine()->checkAndClearException(propValue->location())) {
if (checkErrors)
throw ex.toErrorInfo();
}
@@ -1814,7 +1879,7 @@ void ProjectResolver::evaluateProperty(const Item *item, const QString &propName
}
}
-void ProjectResolver::checkAllowedValues(
+void ProjectResolver::Private::checkAllowedValues(
const QVariant &value, const CodeLocation &loc, const PropertyDeclaration &decl,
const QString &key) const
{
@@ -1835,7 +1900,7 @@ void ProjectResolver::checkAllowedValues(
const auto message = Tr::tr("Value '%1' is not allowed for property '%2'.")
.arg(value, key);
ErrorInfo error(message, loc);
- handlePropertyError(error, m_setupParams, m_logger);
+ handlePropertyError(error, setupParams, logger);
}
};
@@ -1849,18 +1914,25 @@ void ProjectResolver::checkAllowedValues(
}
}
-void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleName,
+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->type() == ItemType::ModuleInstance) {
+ 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, Item *moduleInstance, const QualifiedId &moduleName)
- : valueItem(valueItem), oldScope(valueItem->scope()),
+ EvalPreparer(Item *valueItem, const QualifiedId &moduleName)
+ : valueItem(valueItem),
hadName(!!valueItem->variantProperty(StringConstants::nameProperty()))
{
- valueItem->setScope(moduleInstance);
if (!hadName) {
// Evaluator expects a name here.
valueItem->setProperty(StringConstants::nameProperty(),
@@ -1869,15 +1941,16 @@ void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleNa
}
~EvalPreparer()
{
- valueItem->setScope(oldScope);
if (!hadName)
valueItem->setProperty(StringConstants::nameProperty(), VariantValuePtr());
}
Item * const valueItem;
- Item * const oldScope;
const bool hadName;
};
- EvalPreparer ep(itemValueItem, moduleInstance, moduleName);
+ 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;
}
@@ -1890,32 +1963,31 @@ void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleNa
}
}
-void ProjectResolver::createProductConfig(ResolvedProduct *product)
+void ProjectResolver::Private::createProductConfig(ResolvedProduct *product)
{
- EvalCacheEnabler cachingEnabler(m_evaluator);
- m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory);
- product->moduleProperties->setValue(evaluateModuleValues(m_productContext->item));
- product->productProperties = evaluateProperties(m_productContext->item, m_productContext->item,
+ EvalCacheEnabler cachingEnabler(&evaluator, productContext->product->sourceDirectory);
+ product->moduleProperties->setValue(evaluateModuleValues(productContext->item));
+ product->productProperties = evaluateProperties(productContext->item, productContext->item,
QVariantMap(), true, true);
- m_evaluator->clearPathPropertiesBaseDir();
}
-void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item,
- ProjectContext *projectContext)
+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) {
- ProjectContext subProjectContext = createProjectContext(projectContext);
+ ResolverProjectContext subProjectContext = createProjectContext(projectContext);
(this->*f)(item, &subProjectContext);
} else {
(this->*f)(item, projectContext);
}
}
-ProjectResolver::ProjectContext ProjectResolver::createProjectContext(ProjectContext *parentProjectContext) const
+ResolverProjectContext ProjectResolver::Private::createProjectContext(
+ ResolverProjectContext *parentProjectContext) const
{
- ProjectContext subProjectContext;
+ ResolverProjectContext subProjectContext;
subProjectContext.parentContext = parentProjectContext;
subProjectContext.project = ResolvedProject::create();
parentProjectContext->project->subProjects.push_back(subProjectContext.project);
diff --git a/src/lib/corelib/language/loader.h b/src/lib/corelib/loader/projectresolver.h
index 2c8b08446..2b5a55066 100644
--- a/src/lib/corelib/language/loader.h
+++ b/src/lib/corelib/loader/projectresolver.h
@@ -36,53 +36,49 @@
** $QT_END_LICENSE$
**
****************************************************************************/
-#ifndef QBS_LOADER_H
-#define QBS_LOADER_H
-#include "forward_decls.h"
-#include "moduleproviderinfo.h"
+#ifndef PROJECTRESOLVER_H
+#define PROJECTRESOLVER_H
+
+#include <language/forward_decls.h>
#include <logging/logger.h>
-#include <tools/filetime.h>
+#include <tools/pimpl.h>
+#include <tools/qbs_export.h>
+
+#include <QHash>
+#include <QVariant>
-#include <QtCore/qstringlist.h>
+#include <vector>
namespace qbs {
-class Settings;
class SetupProjectParameters;
namespace Internal {
+class FileTime;
class Logger;
class ProgressObserver;
class ScriptEngine;
+class StoredModuleProviderInfo;
-class QBS_AUTOTEST_EXPORT Loader
+class QBS_AUTOTEST_EXPORT ProjectResolver
{
public:
- Loader(ScriptEngine *engine, Logger logger);
+ ProjectResolver(ScriptEngine *engine, Logger logger);
+ ~ProjectResolver();
void setProgressObserver(ProgressObserver *observer);
- void setSearchPaths(const QStringList &searchPaths);
void setOldProjectProbes(const std::vector<ProbeConstPtr> &oldProbes);
void setOldProductProbes(const QHash<QString, std::vector<ProbeConstPtr>> &oldProbes);
- void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; }
+ void setLastResolveTime(const FileTime &time);
void setStoredProfiles(const QVariantMap &profiles);
void setStoredModuleProviderInfo(const StoredModuleProviderInfo &providerInfo);
- TopLevelProjectPtr loadProject(const SetupProjectParameters &parameters);
-
- static void setupProjectFilePath(SetupProjectParameters &parameters);
+ TopLevelProjectPtr resolve(const SetupProjectParameters &parameters);
private:
- Logger m_logger;
- ProgressObserver *m_progressObserver;
- ScriptEngine * const m_engine;
- QStringList m_searchPaths;
- std::vector<ProbeConstPtr> m_oldProjectProbes;
- QHash<QString, std::vector<ProbeConstPtr>> m_oldProductProbes;
- StoredModuleProviderInfo m_storedModuleProviderInfo;
- QVariantMap m_storedProfiles;
- FileTime m_lastResolveTime;
+ class Private;
+ Pimpl<Private> d;
};
} // namespace Internal
} // namespace qbs
-#endif // QBS_LOADER_H
+#endif // PROJECTRESOLVER_H
diff --git a/src/lib/corelib/loader/projecttreebuilder.cpp b/src/lib/corelib/loader/projecttreebuilder.cpp
new file mode 100644
index 000000000..ec3869895
--- /dev/null
+++ b/src/lib/corelib/loader/projecttreebuilder.cpp
@@ -0,0 +1,297 @@
+/****************************************************************************
+**
+** 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/language/modulemerger.h b/src/lib/corelib/loader/projecttreebuilder.h
index 469dc86c4..a4f08a1f3 100644
--- a/src/lib/corelib/language/modulemerger.h
+++ b/src/lib/corelib/loader/projecttreebuilder.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qbs.
@@ -37,53 +37,56 @@
**
****************************************************************************/
-#ifndef QBS_MODULEMERGER_H
-#define QBS_MODULEMERGER_H
+#pragma once
-#include "item.h"
-#include "qualifiedid.h"
+#include "loaderutils.h"
-#include <logging/logger.h>
-#include <tools/set.h>
-#include <tools/version.h>
+#include <language/forward_decls.h>
+#include <language/moduleproviderinfo.h>
+#include <language/qualifiedid.h>
+#include <tools/pimpl.h>
-#include <QtCore/qhash.h>
+#include <QString>
+#include <QVariant>
namespace qbs {
+class SetupProjectParameters;
namespace Internal {
+class Evaluator;
+class FileTime;
+class Item;
+class ItemPool;
+class ProgressObserver;
-class ModuleMerger {
+class ProjectTreeBuilder
+{
public:
- static void merge(Logger &logger, Item *productItem, const QString &productName,
- Item::Modules *topSortedModules);
+ ProjectTreeBuilder(const SetupProjectParameters &parameters, ItemPool &itemPool,
+ Evaluator &evaluator, Logger &logger);
+ ~ProjectTreeBuilder();
-private:
- ModuleMerger(Logger &logger, Item *productItem, const QString &productName,
- const Item::Modules::iterator &modulesBegin,
- const Item::Modules::iterator &modulesEnd);
-
- void appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName,
- const ValuePtr &sv);
- void mergeModule(Item::PropertyMap *props, const Item::Module &m);
- void replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace);
- void start();
+ struct Result
+ {
+ Item *root = nullptr;
+ std::unordered_map<Item *, ProductInfo> productInfos;
+ std::vector<ProbeConstPtr> projectProbes;
+ StoredModuleProviderInfo storedModuleProviderInfo;
+ Set<QString> qbsFiles;
+ QVariantMap profileConfigs;
+ };
+ Result load();
- static ValuePtr lastInNextChain(const ValuePtr &v);
- static const Item::Module *findModule(const Item *item, const QualifiedId &name);
+ 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);
- Logger &m_logger;
- Item * const m_productItem;
- Item::Module &m_mergedModule;
- Item *m_clonedModulePrototype = nullptr;
- Set<const Item *> m_seenInstances;
- Set<Item *> m_moduleInstanceContainers;
- const bool m_isBaseModule;
- const bool m_isShadowProduct;
- const Item::Modules::iterator m_modulesBegin;
- const Item::Modules::iterator m_modulesEnd;
+private:
+ class Private;
+ Pimpl<Private> d;
};
} // namespace Internal
} // namespace qbs
-
-#endif // QBS_MODULEMERGER_H
diff --git a/src/lib/corelib/parser/qmlerror.cpp b/src/lib/corelib/parser/qmlerror.cpp
index d9fbdd703..42ef8ea29 100644
--- a/src/lib/corelib/parser/qmlerror.cpp
+++ b/src/lib/corelib/parser/qmlerror.cpp
@@ -95,16 +95,12 @@ QmlErrorPrivate::QmlErrorPrivate()
/*!
Creates an empty error object.
*/
-QmlError::QmlError()
-: d(nullptr)
-{
-}
+QmlError::QmlError() = default;
/*!
Creates a copy of \a other.
*/
QmlError::QmlError(const QmlError &other)
-: d(nullptr)
{
*this = other;
}
@@ -118,10 +114,9 @@ QmlError &QmlError::operator=(const QmlError &other)
return *this;
if (!other.d) {
- delete d;
d = nullptr;
} else {
- if (!d) d = new QmlErrorPrivate;
+ if (!d) d = qbs::Internal::makePimpl<QmlErrorPrivate>();
d->url = other.d->url;
d->description = other.d->description;
d->line = other.d->line;
@@ -133,10 +128,7 @@ QmlError &QmlError::operator=(const QmlError &other)
/*!
\internal
*/
-QmlError::~QmlError()
-{
- delete d; d = nullptr;
-}
+QmlError::~QmlError() = default;
/*!
Returns true if this error is valid, otherwise false.
@@ -160,7 +152,7 @@ QUrl QmlError::url() const
*/
void QmlError::setUrl(const QUrl &url)
{
- if (!d) d = new QmlErrorPrivate;
+ if (!d) d = qbs::Internal::makePimpl<QmlErrorPrivate>();
d->url = url;
}
@@ -178,7 +170,7 @@ QString QmlError::description() const
*/
void QmlError::setDescription(const QString &description)
{
- if (!d) d = new QmlErrorPrivate;
+ if (!d) d = qbs::Internal::makePimpl<QmlErrorPrivate>();
d->description = description;
}
@@ -196,7 +188,7 @@ int QmlError::line() const
*/
void QmlError::setLine(int line)
{
- if (!d) d = new QmlErrorPrivate;
+ if (!d) qbs::Internal::makePimpl<QmlErrorPrivate>();
d->line = line;
}
@@ -214,7 +206,7 @@ int QmlError::column() const
*/
void QmlError::setColumn(int column)
{
- if (!d) d = new QmlErrorPrivate;
+ if (!d) qbs::Internal::makePimpl<QmlErrorPrivate>();
d->column = column;
}
diff --git a/src/lib/corelib/parser/qmlerror.h b/src/lib/corelib/parser/qmlerror.h
index cfac506bb..4f7bf8a07 100644
--- a/src/lib/corelib/parser/qmlerror.h
+++ b/src/lib/corelib/parser/qmlerror.h
@@ -40,7 +40,7 @@
#ifndef QQMLERROR_H
#define QQMLERROR_H
-
+#include <tools/pimpl.h>
#include <QtCore/qurl.h>
#include <QtCore/qstring.h>
@@ -73,7 +73,7 @@ public:
QString toString() const;
private:
- QmlErrorPrivate *d;
+ qbs::Internal::Pimpl<QmlErrorPrivate> d;
};
} // namespace QbsQmlJS
diff --git a/src/lib/corelib/tools/error.cpp b/src/lib/corelib/tools/error.cpp
index 16ce47ca9..963089fe8 100644
--- a/src/lib/corelib/tools/error.cpp
+++ b/src/lib/corelib/tools/error.cpp
@@ -276,7 +276,7 @@ void ErrorInfo::clear()
QString ErrorInfo::toString() const
{
QStringList lines;
- for (const ErrorItem &e : qAsConst(d->items)) {
+ for (const ErrorItem &e : std::as_const(d->items)) {
if (e.isBacktraceItem()) {
QString line;
if (!e.description().isEmpty())
diff --git a/src/lib/corelib/tools/executablefinder.cpp b/src/lib/corelib/tools/executablefinder.cpp
index 5bf531551..0bdc861fa 100644
--- a/src/lib/corelib/tools/executablefinder.cpp
+++ b/src/lib/corelib/tools/executablefinder.cpp
@@ -99,7 +99,7 @@ QString ExecutableFinder::findBySuffix(const QString &filePath) const
bool ExecutableFinder::candidateCheck(const QString &directory, const QString &program,
QString &fullProgramPath) const
{
- for (const QString &suffix : qAsConst(m_executableSuffixes)) {
+ for (const QString &suffix : std::as_const(m_executableSuffixes)) {
QString candidate = directory + program + suffix;
qCDebug(lcExec) << "candidate:" << candidate;
QFileInfo fi(candidate);
@@ -123,7 +123,7 @@ QString ExecutableFinder::findInPath(const QString &filePath, const QString &wor
.split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
if (HostOsInfo::isWindowsHost())
pathEnv.prepend(StringConstants::dot());
- for (QString directory : qAsConst(pathEnv)) {
+ for (QString directory : std::as_const(pathEnv)) {
if (directory == StringConstants::dot())
directory = workingDirPath;
if (!directory.isEmpty()) {
diff --git a/src/lib/corelib/tools/fileinfo.h b/src/lib/corelib/tools/fileinfo.h
index f0a09a16b..3ad585f74 100644
--- a/src/lib/corelib/tools/fileinfo.h
+++ b/src/lib/corelib/tools/fileinfo.h
@@ -99,10 +99,10 @@ private:
bool removeFileRecursion(const QFileInfo &f, QString *errorMessage);
-// FIXME: Used by tests.
-bool QBS_EXPORT removeDirectoryWithContents(const QString &path, QString *errorMessage);
-bool QBS_EXPORT copyFileRecursion(const QString &sourcePath, const QString &targetPath,
- bool preserveSymLinks, bool copyDirectoryContents, QString *errorMessage);
+bool QBS_AUTOTEST_EXPORT removeDirectoryWithContents(const QString &path, QString *errorMessage);
+bool QBS_AUTOTEST_EXPORT copyFileRecursion(
+ const QString &sourcePath, const QString &targetPath, bool preserveSymLinks,
+ bool copyDirectoryContents, QString *errorMessage);
} // namespace Internal
} // namespace qbs
diff --git a/src/lib/corelib/tools/launchersocket.cpp b/src/lib/corelib/tools/launchersocket.cpp
index 1489af1e9..4b72d7580 100644
--- a/src/lib/corelib/tools/launchersocket.cpp
+++ b/src/lib/corelib/tools/launchersocket.cpp
@@ -144,7 +144,7 @@ void LauncherSocket::handleRequests()
const auto socket = m_socket.load();
QBS_ASSERT(socket, return);
std::lock_guard<std::mutex> locker(m_requestsMutex);
- for (const QByteArray &request : qAsConst(m_requests))
+ for (const QByteArray &request : std::as_const(m_requests))
socket->write(request);
m_requests.clear();
}
diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp
index 1940b19de..cf5903349 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-131";
+static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-132";
NoBuildGraphError::NoBuildGraphError(const QString &filePath)
: ErrorInfo(Tr::tr("Build graph not found for configuration '%1'. Expected location was '%2'.")
diff --git a/src/lib/corelib/tools/pimpl.h b/src/lib/corelib/tools/pimpl.h
new file mode 100644
index 000000000..ab672aabe
--- /dev/null
+++ b/src/lib/corelib/tools/pimpl.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef PIMPL_H
+#define PIMPL_H
+
+#include <tools/propagate_const.h>
+#include <memory>
+
+namespace qbs {
+namespace Internal {
+
+template<typename T>
+using Pimpl = KDToolBox::propagate_const<std::unique_ptr<T>>;
+
+template<typename T, typename... Args>
+Pimpl<T> makePimpl(Args&&... args)
+{
+ return Pimpl<T>(std::make_unique<T>(std::forward<Args>(args)...));
+}
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // PIMPL_H
diff --git a/src/lib/corelib/tools/propagate_const.h b/src/lib/corelib/tools/propagate_const.h
new file mode 100644
index 000000000..65e35fc8c
--- /dev/null
+++ b/src/lib/corelib/tools/propagate_const.h
@@ -0,0 +1,418 @@
+/****************************************************************************
+** MIT License
+**
+** Copyright (C) 2022-2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
+** Author: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
+**
+** This file is part of KDToolBox (https://github.com/KDAB/KDToolBox).
+**
+** Permission is hereby granted, free of charge, to any person obtaining a copy
+** of this software and associated documentation files (the "Software"), to deal
+** in the Software without restriction, including without limitation the rights
+** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+** copies of the Software, ** and to permit persons to whom the Software is
+** furnished to do so, subject to the following conditions:
+**
+** The above copyright notice and this permission notice (including the next paragraph)
+** shall be included in all copies or substantial portions of the Software.
+**
+** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+** LIABILITY, WHETHER IN AN ACTION OF ** CONTRACT, TORT OR OTHERWISE,
+** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+** DEALINGS IN THE SOFTWARE.
+****************************************************************************/
+
+#ifndef KDTOOLBOX_PROPAGATE_CONST
+#define KDTOOLBOX_PROPAGATE_CONST
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+// Our SFINAE is too noisy and clang-format gets confused.
+// clang-format off
+
+namespace KDToolBox
+{
+
+template <typename T>
+class propagate_const;
+
+namespace detail {
+// Type traits to detect propagate_const specializations
+template <typename T>
+struct is_propagate_const : std::false_type
+{
+};
+
+template <typename T>
+struct is_propagate_const<propagate_const<T>> : std::true_type
+{
+};
+
+// Using SFINAE in a base class to constrain the conversion operators.
+// Otherwise, if we make them function templates and apply SFINAE directly on
+// them, we would not be able to do certain conversions (cf.
+// [over.ics.user/3]).
+template <typename T>
+using propagate_const_element_type = std::remove_reference_t<decltype(*std::declval<T &>())>;
+
+// Const conversion.
+// NON-STANDARD: checks that `const T` is convertible, not `T`.
+// See https://wg21.link/lwg3812
+template <typename T,
+ bool = std::disjunction_v<
+ std::is_pointer<T>,
+ std::is_convertible<const T, const propagate_const_element_type<T> *>
+ >
+ >
+struct propagate_const_const_conversion_operator_base
+{
+};
+
+template <typename T>
+struct propagate_const_const_conversion_operator_base<T, true>
+{
+ constexpr operator const propagate_const_element_type<T> *() const;
+};
+
+// Non-const conversion
+template <typename T,
+ bool = std::disjunction_v<
+ std::is_pointer<T>,
+ std::is_convertible<T, propagate_const_element_type<T> *>
+ >
+ >
+struct propagate_const_non_const_conversion_operator_base
+{
+};
+
+template <typename T>
+struct propagate_const_non_const_conversion_operator_base<T, true>
+{
+ constexpr operator propagate_const_element_type<T> *();
+};
+
+template <typename T>
+struct propagate_const_base
+ : propagate_const_const_conversion_operator_base<T>,
+ propagate_const_non_const_conversion_operator_base<T>
+{};
+
+} // namespace detail
+
+/*
+ TODO: This code could benefit from a C++20 overhaul:
+ * concepts
+ * three-way comparisons
+ * explicit(bool)
+
+ However we can't depend on C++20 yet...
+*/
+
+template <typename T>
+class propagate_const : public detail::propagate_const_base<T>
+{
+public:
+ using element_type = detail::propagate_const_element_type<T>;
+
+ // Special member functions
+ propagate_const() = default;
+ propagate_const(propagate_const &&) = default;
+ propagate_const &operator=(propagate_const &&) = default;
+ propagate_const(const propagate_const &) = delete;
+ propagate_const &operator=(const propagate_const &) = delete;
+ ~propagate_const() = default;
+
+ // Constructor from values
+
+ template <
+ typename U,
+ std::enable_if_t< // This constructor is enabled if:
+ std::conjunction_v<
+ std::is_constructible<T, U>, // 1) we can build a T from a U,
+ std::negation<detail::is_propagate_const<std::decay_t<U>>>, // 2) we are not making a
+ // converting constructor,
+ std::is_convertible<U, T> // 3) and the conversion from U to T is implicit;
+ >,
+ bool> = true>
+ /* implicit */ // then, this constructor is implicit too.
+ propagate_const(U &&other)
+ : m_t(std::forward<U>(other))
+ {
+ }
+
+ template <
+ typename U,
+ std::enable_if_t< // This constructor is enabled if:
+ std::conjunction_v<
+ std::is_constructible<T, U>, // 1) we can build a T from a U,
+ std::negation<detail::is_propagate_const<std::decay_t<U>>>, // 2) we are not making a
+ // converting constructor,
+ std::negation<std::is_convertible<U, T>> // 3) and the conversion from U to T is
+ // explicit;
+ >,
+ bool> = true>
+ explicit // then, this constructor is explicit.
+ propagate_const(U &&other)
+ : m_t(std::forward<U>(other))
+ {
+ }
+
+ // Constructors from other propagate_const (converting constructors)
+ template <typename U,
+ std::enable_if_t< // This constructor is enabled if:
+ std::conjunction_v<std::is_constructible<T, U>, // 1) we can do the conversion,
+ std::is_convertible<U, T> // 2) and the conversion is implicit;
+ >,
+ bool> = true>
+ /* implicit */ // then, this constructor is implicit.
+ constexpr propagate_const(propagate_const<U> &&other)
+ : m_t(std::move(get_underlying(other)))
+ {
+ }
+
+ template <typename U,
+ std::enable_if_t< // This constructor is enabled if:
+ std::conjunction_v<std::is_constructible<T, U>, // 1) we can do the conversion,
+ std::negation<std::is_convertible<U, T>> // 2) and the
+ // conversion is
+ // explicit;
+ >,
+ bool> = true>
+ explicit // then, this constructor is explicit.
+ constexpr propagate_const(propagate_const<U> &&other)
+ : m_t(std::move(get_underlying(other)))
+ {
+ }
+
+ // Converting assignment
+ template <typename U,
+ std::enable_if_t< // This assignment operator is enabled if
+ std::is_convertible_v<U, T>, // the conversion from U to T is implicit
+ bool> = true>
+ constexpr propagate_const &operator=(propagate_const<U> &&other)
+ {
+ m_t = std::move(get_underlying(other));
+ return *this;
+ }
+
+ template <typename U,
+ std::enable_if_t< // This assignment operator is enabled if:
+ std::conjunction_v<
+ std::is_convertible<U, T>, // 1) the conversion from U to T is implicit,
+ std::negation<detail::is_propagate_const<std::decay_t<U>>> // 2) and U is not a
+ // propagate_const
+ >,
+ bool> = true>
+ constexpr propagate_const &operator=(U &&other)
+ {
+ m_t = std::forward<U>(other);
+ return *this;
+ }
+
+ // Swap
+ constexpr void swap(propagate_const &other) noexcept(std::is_nothrow_swappable_v<T>)
+ {
+ using std::swap;
+ swap(m_t, other.m_t);
+ }
+
+ // Const observers
+ constexpr explicit operator bool() const { return static_cast<bool>(m_t); }
+
+ constexpr const element_type *get() const { return get_impl(m_t); }
+
+ constexpr const element_type &operator*() const { return *get(); }
+
+ constexpr const element_type *operator->() const { return get(); }
+
+ // Non-const observers
+ constexpr element_type *get() { return get_impl(m_t); }
+
+ constexpr element_type &operator*() { return *get(); }
+
+ constexpr element_type *operator->() { return get(); }
+
+ // Non-member utilities: extract the contained object
+ template <typename U>
+ friend constexpr auto &get_underlying(propagate_const<U> &p);
+
+ template <typename U>
+ friend constexpr const auto &get_underlying(const propagate_const<U> &p);
+
+private:
+ // Implementation of get() that works with raw pointers and smart
+ // pointers. Similar to std::to_address, but to_address is C++20,
+ // and propagate_const spec does not match it.
+ template <typename U>
+ static constexpr element_type *get_impl(U *u)
+ {
+ return u;
+ }
+
+ template <typename U>
+ static constexpr element_type *get_impl(U &u)
+ {
+ return u.get();
+ }
+
+ template <typename U>
+ static constexpr const element_type *get_impl(const U *u)
+ {
+ return u;
+ }
+
+ template <typename U>
+ static constexpr const element_type *get_impl(const U &u)
+ {
+ return u.get();
+ }
+
+ T m_t;
+};
+
+// Swap
+template <typename T,
+ std::enable_if_t<std::is_swappable_v<T>, bool> = true>
+constexpr void swap(propagate_const<T> &lhs, propagate_const<T> &rhs)
+ noexcept(noexcept(lhs.swap(rhs)))
+{
+ lhs.swap(rhs);
+}
+
+// Implementation of get_underlying
+template <typename T>
+constexpr auto &get_underlying(propagate_const<T> &p)
+{
+ return p.m_t;
+}
+
+template <typename T>
+constexpr const auto &get_underlying(const propagate_const<T> &p)
+{
+ return p.m_t;
+}
+
+// Implementation of the conversion operators
+template <typename T>
+constexpr detail::propagate_const_const_conversion_operator_base<T, true>
+ ::operator const detail::propagate_const_element_type<T> *() const
+{
+ return static_cast<const propagate_const<T> *>(this)->get();
+}
+
+template <typename T>
+constexpr detail::propagate_const_non_const_conversion_operator_base<T, true>
+ ::operator detail::propagate_const_element_type<T> *()
+{
+ return static_cast<propagate_const<T> *>(this)->get();
+}
+
+// Comparisons. As per spec, they're free function templates.
+
+// Comparisons against nullptr.
+template <typename T>
+constexpr bool operator==(const propagate_const<T> &p, std::nullptr_t)
+{
+ return get_underlying(p) == nullptr;
+}
+
+template <typename T>
+constexpr bool operator!=(const propagate_const<T> &p, std::nullptr_t)
+{
+ return get_underlying(p) != nullptr;
+}
+
+template <typename T>
+constexpr bool operator==(std::nullptr_t, const propagate_const<T> &p)
+{
+ return nullptr == get_underlying(p);
+}
+
+template <typename T>
+constexpr bool operator!=(std::nullptr_t, const propagate_const<T> &p)
+{
+ return nullptr == get_underlying(p);
+}
+
+// Comparisons between propagate_const
+#define DEFINE_PROPAGATE_CONST_COMPARISON_OP(op) \
+template <typename T, typename U> \
+ constexpr bool operator op (const propagate_const<T> &lhs, const propagate_const<U> &rhs) \
+{ \
+ return get_underlying(lhs) op get_underlying(rhs); \
+} \
+
+ DEFINE_PROPAGATE_CONST_COMPARISON_OP(==)
+ DEFINE_PROPAGATE_CONST_COMPARISON_OP(!=)
+ DEFINE_PROPAGATE_CONST_COMPARISON_OP(<)
+ DEFINE_PROPAGATE_CONST_COMPARISON_OP(<=)
+ DEFINE_PROPAGATE_CONST_COMPARISON_OP(>)
+ DEFINE_PROPAGATE_CONST_COMPARISON_OP(>=)
+
+#undef DEFINE_PROPAGATE_CONST_COMPARISON_OP
+
+// Comparisons against other (smart) pointers
+#define DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(op) \
+ template <typename T, typename U> \
+ constexpr bool operator op (const propagate_const<T> &lhs, const U &rhs) \
+{ \
+ return get_underlying(lhs) op rhs; \
+} \
+ template <typename T, typename U> \
+ constexpr bool operator op (const T &lhs, const propagate_const<U> &rhs) \
+{ \
+ return lhs op get_underlying(rhs); \
+} \
+
+ DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(==)
+ DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(!=)
+ DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(<)
+ DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(<=)
+ DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(>)
+ DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP(>=)
+
+#undef DEFINE_PROPAGATE_CONST_MIXED_COMPARISON_OP
+
+} // namespace KDToolBox
+
+// std::hash specialization
+namespace std
+{
+template <typename T>
+struct hash<KDToolBox::propagate_const<T>>
+{
+ constexpr size_t operator()(const KDToolBox::propagate_const<T> &t) const
+ noexcept(noexcept(hash<T>{}(get_underlying(t))))
+ {
+ return hash<T>{}(get_underlying(t));
+ }
+};
+
+#define DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(COMP) \
+template <typename T> \
+ struct COMP<KDToolBox::propagate_const<T>> \
+{ \
+ constexpr bool operator()(const KDToolBox::propagate_const<T> &lhs, \
+ const KDToolBox::propagate_const<T> &rhs) \
+ { \
+ return COMP<T>{}(get_underlying(lhs), get_underlying(rhs)); \
+ } \
+}; \
+
+ DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(equal_to)
+ DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(not_equal_to)
+ DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(less)
+ DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(greater)
+ DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(less_equal)
+ DEFINE_COMP_OBJECT_SPECIALIZATION_FOR_PROPAGATE_CONST(greater_equal)
+
+#undef DEFINE_COMP_OBJECT_SPECIALIZATION
+
+} // namespace std
+
+#endif // KDTOOLBOX_PROPAGATE_CONST
diff --git a/src/lib/corelib/tools/qbs_export.h b/src/lib/corelib/tools/qbs_export.h
index 164aa4184..2cfe1cd82 100644
--- a/src/lib/corelib/tools/qbs_export.h
+++ b/src/lib/corelib/tools/qbs_export.h
@@ -53,14 +53,14 @@
#else
# ifdef QBS_LIBRARY
# define QBS_EXPORT QBS_DECL_EXPORT
-# ifdef QBS_ENABLE_UNIT_TESTS
+# ifdef QBS_WITH_TESTS
# define QBS_AUTOTEST_EXPORT QBS_DECL_EXPORT
# else
# define QBS_AUTOTEST_EXPORT
# endif
# else
# define QBS_EXPORT QBS_DECL_IMPORT
-# ifdef QBS_ENABLE_UNIT_TESTS
+# ifdef QBS_WITH_TESTS
# define QBS_AUTOTEST_EXPORT QBS_DECL_IMPORT
# else
# define QBS_AUTOTEST_EXPORT
diff --git a/src/lib/corelib/tools/qbspluginmanager.cpp b/src/lib/corelib/tools/qbspluginmanager.cpp
index d4e92e22a..0816d9d25 100644
--- a/src/lib/corelib/tools/qbspluginmanager.cpp
+++ b/src/lib/corelib/tools/qbspluginmanager.cpp
@@ -79,7 +79,7 @@ QbsPluginManager::~QbsPluginManager()
{
unloadStaticPlugins();
- for (QLibrary * const lib : qAsConst(d->libs)) {
+ for (QLibrary * const lib : std::as_const(d->libs)) {
auto unload = reinterpret_cast<QbsPluginUnloadFunction>(lib->resolve("QbsPluginUnload"));
if (unload)
unload();
diff --git a/src/lib/corelib/tools/scripttools.cpp b/src/lib/corelib/tools/scripttools.cpp
index cf2d1424f..83005c788 100644
--- a/src/lib/corelib/tools/scripttools.cpp
+++ b/src/lib/corelib/tools/scripttools.cpp
@@ -43,6 +43,7 @@
#include <tools/error.h>
#include <QtCore/qdatastream.h>
+#include <QtCore/qdatetime.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonobject.h>
@@ -195,6 +196,11 @@ bool getJsBoolProperty(JSContext *ctx, JSValue obj, const QString &prop)
return JS_VALUE_GET_BOOL(getJsProperty(ctx, obj, prop));
}
+JSValue makeJsArrayBuffer(JSContext *ctx, const QByteArray &s)
+{
+ return ScriptEngine::engineForContext(ctx)->asJsValue(s);
+}
+
JSValue makeJsString(JSContext *ctx, const QString &s)
{
return ScriptEngine::engineForContext(ctx)->asJsValue(s);
@@ -228,6 +234,8 @@ QStringList getJsStringList(JSContext *ctx, JSValue val)
JSValue makeJsVariant(JSContext *ctx, const QVariant &v)
{
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:
@@ -244,11 +252,10 @@ JSValue makeJsVariant(JSContext *ctx, const QVariant &v)
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());
- case QMetaType::QByteArray:
- QBS_ASSERT(!"QByteArray is not a valid type for JS variant", return JS_UNDEFINED);
- [[fallthrough]];
default:
return JS_UNDEFINED;
}
@@ -270,6 +277,13 @@ static QVariant getJsVariantImpl(JSContext *ctx, JSValue val, QList<JSValue> pat
return getJsString(ctx, val);
if (JS_IsBool(val))
return bool(JS_VALUE_GET_BOOL(val));
+ if (JS_IsArrayBuffer(val)) {
+ size_t size = 0;
+ const auto data = JS_GetArrayBuffer(ctx, &size, val);
+ if (!data || !size)
+ return QByteArray();
+ return QByteArray(reinterpret_cast<const char *>(data), size);
+ }
if (JS_IsArray(ctx, val)) {
if (path.contains(val))
return {};
@@ -282,6 +296,15 @@ static QVariant getJsVariantImpl(JSContext *ctx, JSValue val, QList<JSValue> pat
}
return l;
}
+ if (JS_IsDate(val)) {
+ ScopedJsValue toString(ctx, getJsProperty(ctx, val, QLatin1String("toISOString")));
+ if (!JS_IsFunction(ctx, toString))
+ return {};
+ ScopedJsValue dateString(ctx, JS_Call(ctx, toString, val, 0, nullptr));
+ if (!JS_IsString(dateString))
+ return {};
+ return QDateTime::fromString(getJsString(ctx, dateString), Qt::ISODateWithMs);
+ }
if (JS_IsObject(val)) {
if (path.contains(val))
return {};
diff --git a/src/lib/corelib/tools/scripttools.h b/src/lib/corelib/tools/scripttools.h
index 1af7b2b9e..ea7993485 100644
--- a/src/lib/corelib/tools/scripttools.h
+++ b/src/lib/corelib/tools/scripttools.h
@@ -72,6 +72,7 @@ QVariant getJsVariantProperty(JSContext *ctx, JSValueConst obj, const QString &p
QString getJsString(JSContext *ctx, JSValueConst val);
QString getJsString(JSContext *ctx, JSAtom atom);
QBS_AUTOTEST_EXPORT QVariant getJsVariant(JSContext *ctx, JSValueConst val);
+JSValue makeJsArrayBuffer(JSContext *ctx, const QByteArray &s);
JSValue makeJsString(JSContext *ctx, const QString &s);
JSValue makeJsStringList(JSContext *ctx, const QStringList &l);
JSValue makeJsVariant(JSContext *ctx, const QVariant &v);
diff --git a/src/lib/corelib/tools/settingsmodel.cpp b/src/lib/corelib/tools/settingsmodel.cpp
index 31aa0f5f1..89d923496 100644
--- a/src/lib/corelib/tools/settingsmodel.cpp
+++ b/src/lib/corelib/tools/settingsmodel.cpp
@@ -78,7 +78,7 @@ QString Node::uniqueChildName() const
bool unique;
do {
unique = true;
- for (const Node *childNode : qAsConst(children)) {
+ for (const Node *childNode : std::as_const(children)) {
if (childNode->name == newName) {
unique = false;
newName += QLatin1Char('_');
@@ -361,7 +361,7 @@ void SettingsModel::SettingsModelPrivate::addNode(qbs::Internal::Node *parentNod
const QString &currentNamePart, const QStringList &restOfName, const QVariant &value)
{
Node *currentNode = nullptr;
- for (Node * const n : qAsConst(parentNode->children)) {
+ for (Node * const n : std::as_const(parentNode->children)) {
if (n->name == currentNamePart) {
currentNode = n;
break;
@@ -385,7 +385,7 @@ void SettingsModel::SettingsModelPrivate::doSave(const Node *node, const QString
}
const QString newPrefix = prefix + node->name + QLatin1Char('.');
- for (const Node * const child : qAsConst(node->children))
+ for (const Node * const child : std::as_const(node->children))
doSave(child, newPrefix);
}
diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp
index 564ebe873..bc07acfef 100644
--- a/src/lib/corelib/tools/setupprojectparameters.cpp
+++ b/src/lib/corelib/tools/setupprojectparameters.cpp
@@ -47,6 +47,7 @@
#include <tools/qbsassert.h>
#include <tools/scripttools.h>
#include <tools/settings.h>
+#include <tools/stringconstants.h>
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
@@ -104,7 +105,9 @@ public:
} // namespace Internal
-SetupProjectParameters::SetupProjectParameters() : d(new Internal::SetupProjectParametersPrivate)
+using namespace Internal;
+
+SetupProjectParameters::SetupProjectParameters() : d(new SetupProjectParametersPrivate)
{
}
@@ -226,6 +229,44 @@ void SetupProjectParameters::setProjectFilePath(const QString &projectFilePath)
d->projectFilePath = canonicalProjectFilePath;
}
+void SetupProjectParameters::finalizeProjectFilePath()
+{
+ QString filePath = projectFilePath();
+ if (filePath.isEmpty())
+ filePath = QDir::currentPath();
+ const QFileInfo projectFileInfo(filePath);
+ if (!projectFileInfo.exists())
+ throw ErrorInfo(Tr::tr("Project file '%1' cannot be found.").arg(filePath));
+ if (projectFileInfo.isRelative())
+ filePath = projectFileInfo.absoluteFilePath();
+ if (projectFileInfo.isFile()) {
+ setProjectFilePath(filePath);
+ return;
+ }
+ if (!projectFileInfo.isDir())
+ throw ErrorInfo(Tr::tr("Project file '%1' has invalid type.").arg(filePath));
+
+ const QStringList &actualFileNames
+ = QDir(filePath).entryList(StringConstants::qbsFileWildcards(), QDir::Files);
+ if (actualFileNames.empty()) {
+ QString error;
+ if (projectFilePath().isEmpty())
+ error = Tr::tr("No project file given and none found in current directory.\n");
+ else
+ error = Tr::tr("No project file found in directory '%1'.").arg(filePath);
+ throw ErrorInfo(error);
+ }
+ if (actualFileNames.size() > 1) {
+ throw ErrorInfo(Tr::tr("More than one project file found in directory '%1'.")
+ .arg(filePath));
+ }
+ filePath.append(QLatin1Char('/')).append(actualFileNames.front());
+
+ filePath = QDir::current().filePath(filePath);
+ filePath = QDir::cleanPath(filePath);
+ setProjectFilePath(filePath);
+}
+
/*!
* \brief Returns the base path of where to put the build artifacts and store the build graph.
*/
@@ -250,7 +291,7 @@ void SetupProjectParameters::setBuildRoot(const QString &buildRoot)
// Calling mkpath() may be necessary to get the canonical build root, but if we do it,
// it must be reverted immediately afterwards as not to create directories needlessly,
// e.g in the case of a dry run build.
- Internal::DirectoryManager dirManager(buildRoot, Internal::Logger());
+ DirectoryManager dirManager(buildRoot, Logger());
// We don't do error checking here, as this is not a convenient place to report an error.
// If creation of the build directory is not possible, we will get sensible error messages
@@ -361,7 +402,7 @@ static void provideValuesTree(const QVariantMap &values, QVariantMap *valueTree)
const QStringList nameElements = (idx == -1)
? QStringList() << name
: QStringList() << name.left(idx) << name.mid(idx + 1);
- Internal::setConfigProperty(*valueTree, nameElements, it.value());
+ setConfigProperty(*valueTree, nameElements, it.value());
}
}
@@ -402,7 +443,7 @@ static QVariantMap expandedBuildConfigurationInternal(const Profile &profile,
if (err.hasError())
throw err;
if (profileKeys.empty())
- throw ErrorInfo(Internal::Tr::tr("Unknown or empty profile '%1'.").arg(profile.name()));
+ throw ErrorInfo(Tr::tr("Unknown or empty profile '%1'.").arg(profile.name()));
for (const QString &profileKey : profileKeys) {
buildConfig.insert(profileKey, profile.value(profileKey, QVariant(), &err));
if (err.hasError())
@@ -412,7 +453,7 @@ static QVariantMap expandedBuildConfigurationInternal(const Profile &profile,
// (2) Build configuration name.
if (configurationName.isEmpty())
- throw ErrorInfo(Internal::Tr::tr("No build configuration name set."));
+ throw ErrorInfo(Tr::tr("No build configuration name set."));
buildConfig.insert(QStringLiteral("qbs.configurationName"), configurationName);
return buildConfig;
}
diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h
index 5c7bf3715..67bb5298a 100644
--- a/src/lib/corelib/tools/setupprojectparameters.h
+++ b/src/lib/corelib/tools/setupprojectparameters.h
@@ -82,6 +82,7 @@ public:
QString projectFilePath() const;
void setProjectFilePath(const QString &projectFilePath);
+ void finalizeProjectFilePath();
QString buildRoot() const;
void setBuildRoot(const QString &buildRoot);
diff --git a/src/lib/corelib/tools/stlutils.h b/src/lib/corelib/tools/stlutils.h
index 70e3f2b6d..306f37157 100644
--- a/src/lib/corelib/tools/stlutils.h
+++ b/src/lib/corelib/tools/stlutils.h
@@ -46,14 +46,24 @@
namespace qbs {
namespace Internal {
-template <class C>
-C sorted(const C &container)
+template <typename C>
+auto sorted(C &&container)
{
- C result = container;
+ using R = std::remove_cv_t<std::remove_reference_t<C>>;
+ R result(std::forward<C>(container));
std::sort(std::begin(result), std::end(result));
return result;
}
+template <typename C, typename Pred>
+auto sorted(C &&container, Pred &&pred)
+{
+ using R = std::remove_cv_t<std::remove_reference_t<C>>;
+ R result(std::forward<C>(container));
+ std::sort(std::begin(result), std::end(result), std::forward<Pred>(pred));
+ return result;
+}
+
template <typename To, typename From, typename Op>
To transformed(const From &from, Op op)
{
diff --git a/src/lib/msbuild/solution/visualstudiosolution.cpp b/src/lib/msbuild/solution/visualstudiosolution.cpp
index 89b266647..0f6cd4f23 100644
--- a/src/lib/msbuild/solution/visualstudiosolution.cpp
+++ b/src/lib/msbuild/solution/visualstudiosolution.cpp
@@ -72,7 +72,7 @@ QList<IVisualStudioSolutionProject *> VisualStudioSolution::projects() const
QList<VisualStudioSolutionFileProject *> VisualStudioSolution::fileProjects() const
{
QList<VisualStudioSolutionFileProject *> list;
- for (const auto &project : qAsConst(d->projects))
+ for (const auto &project : std::as_const(d->projects))
if (auto fileProject = qobject_cast<VisualStudioSolutionFileProject *>(project))
list.push_back(fileProject);
return list;
@@ -81,7 +81,7 @@ QList<VisualStudioSolutionFileProject *> VisualStudioSolution::fileProjects() co
QList<VisualStudioSolutionFolderProject *> VisualStudioSolution::folderProjects() const
{
QList<VisualStudioSolutionFolderProject *> list;
- for (const auto &project : qAsConst(d->projects))
+ for (const auto &project : std::as_const(d->projects))
if (auto folderProject = qobject_cast<VisualStudioSolutionFolderProject *>(project))
list.push_back(folderProject);
return list;
diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp b/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp
index 2fa3cfa6a..b3b68377f 100644
--- a/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp
+++ b/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp
@@ -230,7 +230,7 @@ struct TargetPageOptions final
}
const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct);
- for (const QVariant &configPath : qAsConst(configPaths)) {
+ for (const QVariant &configPath : std::as_const(configPaths)) {
const QString fullConfigPath = configPath.toString();
// We interested only in a config paths shipped inside of a toolkit.
if (!fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive))
diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp
index 52a19cf81..f2bc8367e 100644
--- a/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp
+++ b/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp
@@ -101,7 +101,7 @@ struct LinkerPageOptions final
if (scatterFiles.count() > 0)
mainScatterFile = scatterFiles.takeFirst();
- for (const auto &scatterFile : qAsConst(scatterFiles)) {
+ for (const auto &scatterFile : std::as_const(scatterFiles)) {
const auto control = QStringLiteral("--scatter %1").arg(scatterFile);
miscControls.push_back(control);
}
diff --git a/src/plugins/generator/makefilegenerator/makefilegenerator.cpp b/src/plugins/generator/makefilegenerator/makefilegenerator.cpp
index c53dccd31..690d83569 100644
--- a/src/plugins/generator/makefilegenerator/makefilegenerator.cpp
+++ b/src/plugins/generator/makefilegenerator/makefilegenerator.cpp
@@ -332,15 +332,15 @@ void qbs::MakefileGenerator::generate()
}
stream << "all:";
- for (const QString &target : qAsConst(allDefaultTargets))
+ for (const QString &target : std::as_const(allDefaultTargets))
stream << ' ' << target;
stream << '\n';
stream << "install:";
- for (const QString &target : qAsConst(allDefaultTargets))
+ for (const QString &target : std::as_const(allDefaultTargets))
stream << ' ' << "install-" << target;
stream << '\n';
stream << "clean:";
- for (const QString &target : qAsConst(allTargets))
+ for (const QString &target : std::as_const(allTargets))
stream << ' ' << "clean-" << target;
stream << '\n';
if (!filesCreatedByJsCommands.empty()) {
diff --git a/src/plugins/generator/visualstudio/visualstudiogenerator.cpp b/src/plugins/generator/visualstudio/visualstudiogenerator.cpp
index d0c367dd7..d6add9c6f 100644
--- a/src/plugins/generator/visualstudio/visualstudiogenerator.cpp
+++ b/src/plugins/generator/visualstudio/visualstudiogenerator.cpp
@@ -189,7 +189,7 @@ void VisualStudioGenerator::addPropertySheets(const GeneratableProject &project)
void VisualStudioGenerator::addPropertySheets(
const std::shared_ptr<MSBuildTargetProject> &targetProject)
{
- for (const auto &pair : qAsConst(d->propertySheetNames)) {
+ for (const auto &pair : std::as_const(d->propertySheetNames)) {
targetProject->appendPropertySheet(
QStringLiteral("$(SolutionDir)\\") + pair.first, pair.second);
}
diff --git a/src/shared/quickjs/quickjs.c b/src/shared/quickjs/quickjs.c
index f90fb9e4f..578fd3a26 100644
--- a/src/shared/quickjs/quickjs.c
+++ b/src/shared/quickjs/quickjs.c
@@ -9822,6 +9822,14 @@ BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, BOOL val)
return TRUE;
}
+JS_BOOL JS_IsArrayBuffer(JSValueConst v)
+{
+ if (!JS_IsObject(v))
+ return FALSE;
+ JSObject *p = JS_VALUE_GET_OBJ(v);
+ return p->class_id == JS_CLASS_ARRAY_BUFFER || p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER;
+}
+
BOOL JS_IsError(JSContext *ctx, JSValueConst val)
{
JSObject *p;
@@ -54313,3 +54321,23 @@ JS_BOOL JS_IsRegExp(JSContext *ctx, JSValue val)
return FALSE;
return JS_VALUE_GET_OBJ(val)->class_id == JS_CLASS_REGEXP;
}
+
+int JS_IsDate(JSValue v)
+{
+ JSObject *p;
+ if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT)
+ return FALSE;
+ return JS_VALUE_GET_OBJ(v)->class_id == JS_CLASS_DATE;
+}
+
+JSValue JS_NewDate(JSContext *ctx, const char *s)
+{
+ JSValue dateString = JS_NewString(ctx, s);
+ JSAtom constrAtom = JS_NewAtom(ctx, "Date");
+ JSValue constr = JS_GetGlobalVar(ctx, constrAtom, FALSE);
+ JSValue date = js_date_constructor(ctx, constr, 1, &dateString);
+ JS_FreeValue(ctx, constr);
+ JS_FreeValue(ctx, dateString);
+ JS_FreeAtom(ctx, constrAtom);
+ return date;
+}
diff --git a/src/shared/quickjs/quickjs.h b/src/shared/quickjs/quickjs.h
index a5adf0cd2..fbea4af3d 100644
--- a/src/shared/quickjs/quickjs.h
+++ b/src/shared/quickjs/quickjs.h
@@ -665,6 +665,10 @@ JS_BOOL JS_IsFunction(JSContext* ctx, JSValueConst val);
JS_BOOL JS_IsRegExp(JSContext* ctx, JSValueConst val);
JS_BOOL JS_IsConstructor(JSContext* ctx, JSValueConst val);
JS_BOOL JS_SetConstructorBit(JSContext *ctx, JSValueConst func_obj, JS_BOOL val);
+JS_BOOL JS_IsArrayBuffer(JSValueConst v);
+
+JSValue JS_NewDate(JSContext *ctx, const char *s);
+JS_BOOL JS_IsDate(JSValueConst v);
JSValue JS_NewArray(JSContext *ctx);
int JS_IsArray(JSContext *ctx, JSValueConst val);
diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp
index c82d6f503..94d02c15c 100644
--- a/tests/auto/api/tst_api.cpp
+++ b/tests/auto/api/tst_api.cpp
@@ -1292,7 +1292,7 @@ void TestApi::fallbackGcc()
QVERIFY(project.isValid());
QList<qbs::ProductData> products = project.allProducts();
QCOMPARE(products.size(), 2);
- for (const qbs::ProductData &p : qAsConst(products)) {
+ for (const qbs::ProductData &p : std::as_const(products)) {
if (p.profile() == "unixProfile") {
qbs::PropertyMap moduleProps = p.moduleProperties();
QCOMPARE(moduleProps.getModuleProperty("qbs", "targetOS").toStringList(),
@@ -1435,7 +1435,7 @@ void TestApi::infiniteLoopResolving()
m_logSink, nullptr));
QTimer::singleShot(1000, setupJob.get(), &qbs::AbstractJob::cancel);
QVERIFY(waitForFinished(setupJob.get(), testTimeoutInMsecs()));
- QVERIFY2(setupJob->error().toString().toLower().contains("interrupted"),
+ QVERIFY2(setupJob->error().toString().toLower().contains("cancel"),
qPrintable(setupJob->error().toString()));
}
@@ -1564,7 +1564,7 @@ void TestApi::linkDynamicAndStaticLibs()
if (profileToolchain(buildProfile).contains("gcc")) {
static const std::regex appLinkCmdRex(" -o [^ ]*/HelloWorld" QBS_HOST_EXE_SUFFIX " ");
QString appLinkCmd;
- for (const QString &line : qAsConst(bdr.descriptionLines)) {
+ for (const QString &line : std::as_const(bdr.descriptionLines)) {
const auto ln = line.toStdString();
if (std::regex_search(ln, appLinkCmdRex)) {
appLinkCmd = line;
@@ -1597,7 +1597,7 @@ void TestApi::linkStaticAndDynamicLibs()
if (profileToolchain(buildProfile).contains("gcc")) {
static const std::regex appLinkCmdRex(" -o [^ ]*/HelloWorld" QBS_HOST_EXE_SUFFIX " ");
QString appLinkCmd;
- for (const QString &line : qAsConst(bdr.descriptionLines)) {
+ for (const QString &line : std::as_const(bdr.descriptionLines)) {
const auto ln = line.toStdString();
if (std::regex_search(ln, appLinkCmdRex)) {
appLinkCmd = line;
@@ -1661,7 +1661,7 @@ void TestApi::localProfiles()
qbs::ProductData libClang;
qbs::ProductData appDebug;
qbs::ProductData appRelease;
- for (const qbs::ProductData &p : qAsConst(products)) {
+ for (const qbs::ProductData &p : std::as_const(products)) {
if (p.name() == "lib") {
if (p.profile() == "mingwProfile")
libMingw = p;
@@ -1731,7 +1731,7 @@ void TestApi::localProfiles()
products = project.allProducts();
QCOMPARE(products.size(), 4);
int clangProfiles = 0;
- for (const qbs::ProductData &p : qAsConst(products)) {
+ for (const qbs::ProductData &p : std::as_const(products)) {
if (p.profile() == "clangProfile") {
++clangProfiles;
moduleProps = p.moduleProperties();
@@ -1887,26 +1887,23 @@ void TestApi::multiArch()
QFile p2ArtifactInstalled(installRoot + "/host/host-tool.output");
QVERIFY2(p2ArtifactInstalled.exists(), qPrintable(p2ArtifactInstalled.fileName()));
- // Error check: Try to build for the same profile twice.
+ // Specifying the same profile twice should not result in an attempt to multiplex.
overriddenValues.insert("project.targetProfile", hostProfile.name());
setupParams.setOverriddenValues(overriddenValues);
setupJob.reset(project.setupProject(setupParams, m_logSink, nullptr));
waitForFinished(setupJob.get());
- QVERIFY(setupJob->error().hasError());
- QVERIFY2(setupJob->error().toString().contains("Duplicate entry 'host' in qbs.profiles."),
- qPrintable(setupJob->error().toString()));
+ QVERIFY(!setupJob->error().hasError());
+ QCOMPARE(int(setupJob->project().projectData().products().size()), 2);
- // Error check: Try to build for the same profile twice, this time attaching
- // the properties via the product name.
+ // The same, but this time attaching the properties via the product name.
overriddenValues.remove(QStringLiteral("project.targetProfile"));
overriddenValues.insert("products.p1.myProfiles",
targetProfile.name() + ',' + targetProfile.name());
setupParams.setOverriddenValues(overriddenValues);
setupJob.reset(project.setupProject(setupParams, m_logSink, nullptr));
waitForFinished(setupJob.get());
- QVERIFY(setupJob->error().hasError());
- QVERIFY2(setupJob->error().toString().contains("Duplicate entry 'target' in qbs.profiles."),
- qPrintable(setupJob->error().toString()));
+ QVERIFY(!setupJob->error().hasError());
+ QCOMPARE(int(setupJob->project().projectData().products().size()), 2);
}
struct ProductDataSelector
@@ -2713,12 +2710,13 @@ void TestApi::restoredWarnings()
waitForFinished(job.get());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
job.reset(nullptr);
- QCOMPARE(toSet(m_logSink->warnings).size(), 2);
+ QCOMPARE(toSet(m_logSink->warnings).size(), 3);
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("Property 'blubb' is not declared")
+ || msg.contains("Product 'theProduct' had errors and was disabled"),
qPrintable(msg));
}
m_logSink->warnings.clear();
@@ -2728,7 +2726,7 @@ void TestApi::restoredWarnings()
waitForFinished(job.get());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
job.reset(nullptr);
- QCOMPARE(toSet(m_logSink->warnings).size(), 2);
+ QCOMPARE(toSet(m_logSink->warnings).size(), 3);
m_logSink->warnings.clear();
// Re-resolving with changes: Errors come from the re-resolving, stored ones must be suppressed.
@@ -2739,13 +2737,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); // One more for the additional group
+ QCOMPARE(toSet(m_logSink->warnings).size(), 4); // 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("blubb.cpp' does not exist")
+ || 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 a4c4beedd..bf6b3d219 100644
--- a/tests/auto/auto.qbs
+++ b/tests/auto/auto.qbs
@@ -9,6 +9,7 @@ Project {
"blackbox/blackbox-examples.qbs",
"blackbox/blackbox-java.qbs",
"blackbox/blackbox-joblimits.qbs",
+ "blackbox/blackbox-providers.qbs",
"blackbox/blackbox-qt.qbs",
"blackbox/blackbox-windows.qbs",
"blackbox/blackbox.qbs",
diff --git a/tests/auto/blackbox/CMakeLists.txt b/tests/auto/blackbox/CMakeLists.txt
index 5b5376064..1d90ec95b 100644
--- a/tests/auto/blackbox/CMakeLists.txt
+++ b/tests/auto/blackbox/CMakeLists.txt
@@ -63,6 +63,14 @@ add_qbs_test(blackbox-joblimits
tst_blackboxjoblimits.cpp
)
+add_qbs_test(blackbox-providers
+ SOURCES
+ ../shared.h
+ tst_blackboxbase.cpp
+ tst_blackboxbase.h
+ tst_blackboxproviders.cpp
+ )
+
add_qbs_test(blackbox-qt
SOURCES
../shared.h
diff --git a/tests/auto/blackbox/blackbox-providers.qbs b/tests/auto/blackbox/blackbox-providers.qbs
new file mode 100644
index 000000000..95ebaa423
--- /dev/null
+++ b/tests/auto/blackbox/blackbox-providers.qbs
@@ -0,0 +1,21 @@
+import qbs.Utilities
+
+QbsAutotest {
+ testName: "blackbox-providers"
+ Depends { name: "qbs_app" }
+ Depends { name: "qbs-setup-toolchains" }
+ Group {
+ name: "testdata"
+ prefix: "testdata-providers/"
+ files: ["**/*"]
+ fileTags: []
+ }
+ files: [
+ "../shared.h",
+ "tst_blackboxbase.cpp",
+ "tst_blackboxbase.h",
+ "tst_blackboxproviders.cpp",
+ "tst_blackboxproviders.h",
+ ]
+ cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)])
+}
diff --git a/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs
index 61accd6dd..c7b9b3de2 100644
--- a/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs
+++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs
@@ -7,7 +7,10 @@ Project {
qbs.targetPlatform: "android"
Properties { condition: qbs.toolchain.includes("clang"); Android.ndk.appStl: "c++_shared" }
Android.ndk.appStl: "stlport_shared"
- qbs.architectures: !qbs.architecture ? ["armv7a", "x86"] : undefined
+ Properties {
+ qbs.architectures: !qbs.architecture ? ["armv7a", "x86"] : undefined
+ overrideListProperties: true
+ }
cpp.useRPaths: false
}
diff --git a/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/ByteArray-Info.plist b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/ByteArray-Info.plist
new file mode 100644
index 000000000..df0429f25
--- /dev/null
+++ b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/ByteArray-Info.plist
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>DataKey</key>
+ <!--The data value-->
+ <data>VGhlIGRhdGEgdmFsdWU=</data>
+ <key>StringKey</key>
+ <string>The string value</string>
+</dict>
+</plist>
diff --git a/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/byteArrayInfoPlist.qbs b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/byteArrayInfoPlist.qbs
new file mode 100644
index 000000000..4df0886ff
--- /dev/null
+++ b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/byteArrayInfoPlist.qbs
@@ -0,0 +1,37 @@
+import qbs.BundleTools
+import qbs.TextFile
+
+CppApplication {
+ Depends { name: "bundle" }
+ cpp.minimumMacosVersion: "10.7"
+ files: ["main.c", "ByteArray-Info.plist"]
+ type: base.concat(["txt_output"])
+
+ Properties {
+ condition: qbs.targetOS.includes("darwin")
+ bundle.isBundle: true
+ bundle.identifierPrefix: "com.test"
+ }
+
+ Rule {
+ inputs: ["aggregate_infoplist"]
+ Artifact {
+ filePath: input.fileName + ".out"
+ fileTags: ["txt_output"]
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "generating" + output.fileName + " from " + input.fileName;
+ cmd.highlight = "codegen";
+ cmd.sourceCode = function() {
+ var plist = new BundleTools.infoPlistContents(input.filePath);
+ var content = plist["DataKey"];
+ var int8view = new Uint8Array(content);
+ file = new TextFile(output.filePath, TextFile.WriteOnly);
+ file.write(String.fromCharCode.apply(null, int8view));
+ file.close();
+ }
+ return [cmd];
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/main.c b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/main.c
new file mode 100644
index 000000000..76e819701
--- /dev/null
+++ b/tests/auto/blackbox/testdata-apple/byteArrayInfoPlist/main.c
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/tests/auto/blackbox/testdata/fallback-module-provider/fallback-module-provider.qbs b/tests/auto/blackbox/testdata-providers/fallback-module-provider/fallback-module-provider.qbs
index a798e15b3..a798e15b3 100644
--- a/tests/auto/blackbox/testdata/fallback-module-provider/fallback-module-provider.qbs
+++ b/tests/auto/blackbox/testdata-providers/fallback-module-provider/fallback-module-provider.qbs
diff --git a/tests/auto/blackbox/testdata/fallback-module-provider/libdir/qbsmetatestmodule.pc b/tests/auto/blackbox/testdata-providers/fallback-module-provider/libdir/qbsmetatestmodule.pc
index ae4daba89..ae4daba89 100644
--- a/tests/auto/blackbox/testdata/fallback-module-provider/libdir/qbsmetatestmodule.pc
+++ b/tests/auto/blackbox/testdata-providers/fallback-module-provider/libdir/qbsmetatestmodule.pc
diff --git a/tests/auto/blackbox/testdata/fallback-module-provider/main.cpp b/tests/auto/blackbox/testdata-providers/fallback-module-provider/main.cpp
index 442b755bf..442b755bf 100644
--- a/tests/auto/blackbox/testdata/fallback-module-provider/main.cpp
+++ b/tests/auto/blackbox/testdata-providers/fallback-module-provider/main.cpp
diff --git a/tests/auto/blackbox/testdata-providers/module-providers-cache/module-providers-cache.qbs b/tests/auto/blackbox/testdata-providers/module-providers-cache/module-providers-cache.qbs
new file mode 100644
index 000000000..508ed84d2
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/module-providers-cache/module-providers-cache.qbs
@@ -0,0 +1,11 @@
+Project {
+ qbsModuleProviders: ["provider_a"]
+ name: "project"
+ property string dummyProp
+
+ Product {
+ name: "p1"
+ Depends { name: "qbsothermodule" }
+ Depends { name: "qbsmetatestmodule" }
+ }
+}
diff --git a/tests/auto/blackbox/testdata-providers/module-providers-cache/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/module-providers-cache/module-providers/provider_a.qbs
new file mode 100644
index 000000000..782cf7d25
--- /dev/null
+++ b/tests/auto/blackbox/testdata-providers/module-providers-cache/module-providers/provider_a.qbs
@@ -0,0 +1,9 @@
+import "../../qbs-module-providers-helpers.js" as Helpers
+
+ModuleProvider {
+ relativeSearchPaths: {
+ Helpers.writeModule(outputBaseDir, "qbsmetatestmodule", "from_provider_a");
+ Helpers.writeModule(outputBaseDir, "qbsothermodule", "from_provider_a");
+ return "";
+ }
+}
diff --git a/tests/auto/blackbox/testdata/module-providers/main.cpp b/tests/auto/blackbox/testdata-providers/module-providers/main.cpp
index 85a4f551c..85a4f551c 100644
--- a/tests/auto/blackbox/testdata/module-providers/main.cpp
+++ b/tests/auto/blackbox/testdata-providers/module-providers/main.cpp
diff --git a/tests/auto/blackbox/testdata/module-providers/module-providers.qbs b/tests/auto/blackbox/testdata-providers/module-providers/module-providers.qbs
index e83c80bb1..e83c80bb1 100644
--- a/tests/auto/blackbox/testdata/module-providers/module-providers.qbs
+++ b/tests/auto/blackbox/testdata-providers/module-providers/module-providers.qbs
diff --git a/tests/auto/blackbox/testdata/module-providers/module-providers/mygenerator/provider.qbs b/tests/auto/blackbox/testdata-providers/module-providers/module-providers/mygenerator/provider.qbs
index dae02c03a..dae02c03a 100644
--- a/tests/auto/blackbox/testdata/module-providers/module-providers/mygenerator/provider.qbs
+++ b/tests/auto/blackbox/testdata-providers/module-providers/module-providers/mygenerator/provider.qbs
diff --git a/tests/auto/blackbox/testdata/module-providers/module-providers/othergenerator/provider.qbs b/tests/auto/blackbox/testdata-providers/module-providers/module-providers/othergenerator/provider.qbs
index 66557037c..66557037c 100644
--- a/tests/auto/blackbox/testdata/module-providers/module-providers/othergenerator/provider.qbs
+++ b/tests/auto/blackbox/testdata-providers/module-providers/module-providers/othergenerator/provider.qbs
diff --git a/tests/auto/blackbox/testdata/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..8a7c7d6ed 100644
--- a/tests/auto/blackbox/testdata/probe-in-module-provider/module-providers/provider_a.qbs
+++ b/tests/auto/blackbox/testdata-providers/probe-in-module-provider/module-providers/provider_a.qbs
diff --git a/tests/auto/blackbox/testdata/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..cb346beeb 100644
--- a/tests/auto/blackbox/testdata/probe-in-module-provider/probe-in-module-provider.qbs
+++ b/tests/auto/blackbox/testdata-providers/probe-in-module-provider/probe-in-module-provider.qbs
diff --git a/tests/auto/blackbox/testdata/providers-properties/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/providers-properties/module-providers/provider_a.qbs
index ab9d475d8..ab9d475d8 100644
--- a/tests/auto/blackbox/testdata/providers-properties/module-providers/provider_a.qbs
+++ b/tests/auto/blackbox/testdata-providers/providers-properties/module-providers/provider_a.qbs
diff --git a/tests/auto/blackbox/testdata/providers-properties/module-providers/provider_b.qbs b/tests/auto/blackbox/testdata-providers/providers-properties/module-providers/provider_b.qbs
index 1b2a79979..1b2a79979 100644
--- a/tests/auto/blackbox/testdata/providers-properties/module-providers/provider_b.qbs
+++ b/tests/auto/blackbox/testdata-providers/providers-properties/module-providers/provider_b.qbs
diff --git a/tests/auto/blackbox/testdata/providers-properties/providers-properties.qbs b/tests/auto/blackbox/testdata-providers/providers-properties/providers-properties.qbs
index 258a973fa..258a973fa 100644
--- a/tests/auto/blackbox/testdata/providers-properties/providers-properties.qbs
+++ b/tests/auto/blackbox/testdata-providers/providers-properties/providers-properties.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-properties-in-providers/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-properties-in-providers/module-providers/provider_a.qbs
index 95c89cd1c..95c89cd1c 100644
--- a/tests/auto/blackbox/testdata/qbs-module-properties-in-providers/module-providers/provider_a.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-properties-in-providers/module-providers/provider_a.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs
index a338a220d..c2fc58299 100644
--- a/tests/auto/blackbox/testdata/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-properties-in-providers/qbs-module-properties-in-providers.qbs
@@ -4,12 +4,12 @@ Project {
Profile {
name: "profile1"
- qbs.sysroot: "sysroot1"
+ qbs.sysroot: "/sysroot1"
}
Profile {
name: "profile2"
- qbs.sysroot: "sysroot2"
+ qbs.sysroot: "/sysroot2"
}
Product {
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers-cli-override/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/module-providers/provider_a.qbs
index d34d1cac5..d34d1cac5 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers-cli-override/module-providers/provider_a.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/module-providers/provider_a.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers-cli-override/module-providers/provider_b.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/module-providers/provider_b.qbs
index 767e30923..767e30923 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers-cli-override/module-providers/provider_b.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/module-providers/provider_b.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers-cli-override/qbs-module-providers-cli-override.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/qbs-module-providers-cli-override.qbs
index 6f94ab207..6f94ab207 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers-cli-override/qbs-module-providers-cli-override.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers-cli-override/qbs-module-providers-cli-override.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers-compatibility/module-providers/named_provider.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/module-providers/named_provider.qbs
index 07114b5ef..07114b5ef 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers-compatibility/module-providers/named_provider.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/module-providers/named_provider.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers-compatibility/module-providers/qbsmetatestmodule/provider.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/module-providers/qbsmetatestmodule/provider.qbs
index b04a52261..b04a52261 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers-compatibility/module-providers/qbsmetatestmodule/provider.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/module-providers/qbsmetatestmodule/provider.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers-compatibility/qbs-module-providers-compatibility.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/qbs-module-providers-compatibility.qbs
index 7885b540a..7885b540a 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers-compatibility/qbs-module-providers-compatibility.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers-compatibility/qbs-module-providers-compatibility.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers-helpers.js b/tests/auto/blackbox/testdata-providers/qbs-module-providers-helpers.js
index 8b6d9e275..8b6d9e275 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers-helpers.js
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers-helpers.js
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers/module-providers/provider_a.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers/module-providers/provider_a.qbs
index d34d1cac5..d34d1cac5 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers/module-providers/provider_a.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers/module-providers/provider_a.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers/module-providers/provider_b.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers/module-providers/provider_b.qbs
index 767e30923..767e30923 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers/module-providers/provider_b.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers/module-providers/provider_b.qbs
diff --git a/tests/auto/blackbox/testdata/qbs-module-providers/qbs-module-providers.qbs b/tests/auto/blackbox/testdata-providers/qbs-module-providers/qbs-module-providers.qbs
index 00776a62e..00776a62e 100644
--- a/tests/auto/blackbox/testdata/qbs-module-providers/qbs-module-providers.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbs-module-providers/qbs-module-providers.qbs
diff --git a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libA.cpp b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libA.cpp
index 0c5274415..0c5274415 100644
--- a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libA.cpp
+++ b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libA.cpp
diff --git a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libA.h b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libA.h
index ddaaf1609..ddaaf1609 100644
--- a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libA.h
+++ b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libA.h
diff --git a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libs.qbs b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libs.qbs
index b473083c6..b473083c6 100644
--- a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/libs/libs.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/libs/libs.qbs
diff --git a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/main.cpp b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/main.cpp
index 5fa0f7eed..5fa0f7eed 100644
--- a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/main.cpp
+++ b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/main.cpp
diff --git a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/qbspkgconfig-module-provider.qbs b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/qbspkgconfig-module-provider.qbs
index d2b3654ae..d2b3654ae 100644
--- a/tests/auto/blackbox/testdata/qbspkgconfig-module-provider/qbspkgconfig-module-provider.qbs
+++ b/tests/auto/blackbox/testdata-providers/qbspkgconfig-module-provider/qbspkgconfig-module-provider.qbs
diff --git a/tests/auto/blackbox/testdata/conflicting-property-values/conflicting-property-values.qbs b/tests/auto/blackbox/testdata/conflicting-property-values/conflicting-property-values.qbs
new file mode 100644
index 000000000..23b6ee5a3
--- /dev/null
+++ b/tests/auto/blackbox/testdata/conflicting-property-values/conflicting-property-values.qbs
@@ -0,0 +1,41 @@
+Project {
+ Product {
+ name: "low"
+ Export { property string prop: "low"; property string prop2: "low" }
+ }
+ Product {
+ name: "higher1"
+ Export { Depends { name: "low" } low.prop: "higher1" }
+ }
+ Product {
+ name: "higher2"
+ Export { Depends { name: "low" } low.prop: "higher2" }
+ }
+ Product {
+ name: "highest1"
+ Export {
+ Depends { name: "low" }
+ Depends { name: "higher1" }
+ Depends { name: "higher2" }
+ low.prop: "highest1"
+ low.prop2: "highest"
+ }
+ }
+ Product {
+ name: "highest2"
+ Export {
+ Depends { name: "low" }
+ Depends { name: "higher1" }
+ Depends { name: "higher2" }
+ low.prop: "highest2"
+ low.prop2: "highest"
+ }
+ }
+ Product {
+ name: "toplevel"
+ Depends { name: "highest1" }
+ Depends { name: "highest2" }
+ low.prop: name
+ property bool dummy: { console.info("final prop value: " + low.prop); }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/date-property/date-property.qbs b/tests/auto/blackbox/testdata/date-property/date-property.qbs
new file mode 100644
index 000000000..ffd584802
--- /dev/null
+++ b/tests/auto/blackbox/testdata/date-property/date-property.qbs
@@ -0,0 +1,18 @@
+Product {
+ type: "date"
+ property var theDate: new Date(1999, 11, 31);
+ Rule {
+ multiplex: true
+ Artifact { filePath: "dummy"; fileTags: "date" }
+ prepare: {
+ var cmd = new JavaScriptCommand;
+ cmd.silent = true;
+ cmd.sourceCode = function() {
+ var d = product.theDate;
+ console.info("The stored date was " + d.getFullYear() + '-' + (d.getMonth() + 1) + '-'
+ + d.getDate());
+ };
+ return cmd;
+ }
+ }
+}
diff --git a/tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs b/tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs
index e699672a8..7d1550312 100644
--- a/tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs
+++ b/tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs
@@ -1,5 +1,3 @@
-import qbs // FIXME: Don't remove this import because then the test fails!
-
Product {
Depends { name: "themodule" }
themodule.newProp: true
diff --git a/tests/auto/blackbox/testdata/exports-qbs/lib.qbs b/tests/auto/blackbox/testdata/exports-qbs/lib.qbs
index 951b0fa74..01f89a221 100644
--- a/tests/auto/blackbox/testdata/exports-qbs/lib.qbs
+++ b/tests/auto/blackbox/testdata/exports-qbs/lib.qbs
@@ -52,7 +52,7 @@ DynamicLibrary {
condition: true
prefixMapping: [{
prefix: includeDir,
- replacement: FileInfo.joinPaths(qbs.installPrefix, exportingProduct.headersInstallDir)
+ replacement: FileInfo.joinPaths(exportingProduct.qbs.installPrefix, exportingProduct.headersInstallDir)
}]
}
}
diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp
index fd81dd494..424d08642 100644
--- a/tests/auto/blackbox/tst_blackbox.cpp
+++ b/tests/auto/blackbox/tst_blackbox.cpp
@@ -1070,11 +1070,11 @@ void TestBlackbox::deprecatedProperty()
QVERIFY(runQbs(params) != 0);
m_qbsStderr = QDir::fromNativeSeparators(QString::fromLocal8Bit(m_qbsStderr)).toLocal8Bit();
const bool hasExpiringWarning = m_qbsStderr.contains(QByteArray(
- "deprecated-property.qbs:6:29 The property 'expiringProp' is "
+ "deprecated-property.qbs:4:29 The property 'expiringProp' is "
"deprecated and will be removed in Qbs ") + version.toLocal8Bit());
QVERIFY2(expiringWarning == hasExpiringWarning, m_qbsStderr.constData());
const bool hasRemovedOutput = m_qbsStderr.contains(
- "deprecated-property.qbs:7:28 The property 'veryOldProp' can no "
+ "deprecated-property.qbs:5:28 The property 'veryOldProp' can no "
"longer be used. It was removed in Qbs 1.3.0.");
QVERIFY2(hasRemovedOutput == !expiringError, m_qbsStderr.constData());
QVERIFY2(m_qbsStderr.contains("Property 'forgottenProp' was scheduled for removal in version "
@@ -1719,7 +1719,7 @@ void TestBlackbox::clean()
QVERIFY(!QFile(appExeFilePath).exists());
QVERIFY(!QFile(depObjectFilePath).exists());
QVERIFY(!QFile(depLibFilePath).exists());
- for (const QString &symLink : qAsConst(symlinks))
+ for (const QString &symLink : std::as_const(symlinks))
QVERIFY2(!symlinkExists(symLink), qPrintable(symLink));
// Remove all, with a forced re-resolve in between.
@@ -1738,7 +1738,7 @@ void TestBlackbox::clean()
QVERIFY(!QFile(appExeFilePath).exists());
QVERIFY(!QFile(depObjectFilePath).exists());
QVERIFY(!QFile(depLibFilePath).exists());
- for (const QString &symLink : qAsConst(symlinks))
+ for (const QString &symLink : std::as_const(symlinks))
QVERIFY2(!symlinkExists(symLink), qPrintable(symLink));
// Dry run.
@@ -1750,7 +1750,7 @@ void TestBlackbox::clean()
QVERIFY(regularFileExists(appExeFilePath));
QVERIFY(regularFileExists(depObjectFilePath));
QVERIFY(regularFileExists(depLibFilePath));
- for (const QString &symLink : qAsConst(symlinks))
+ for (const QString &symLink : std::as_const(symlinks))
QVERIFY2(symlinkExists(symLink), qPrintable(symLink));
// Product-wise, dependency only.
@@ -1764,7 +1764,7 @@ void TestBlackbox::clean()
QVERIFY(regularFileExists(appExeFilePath));
QVERIFY(!QFile(depObjectFilePath).exists());
QVERIFY(!QFile(depLibFilePath).exists());
- for (const QString &symLink : qAsConst(symlinks))
+ for (const QString &symLink : std::as_const(symlinks))
QVERIFY2(!symlinkExists(symLink), qPrintable(symLink));
// Product-wise, dependent product only.
@@ -1778,7 +1778,7 @@ void TestBlackbox::clean()
QVERIFY(!QFile(appExeFilePath).exists());
QVERIFY(regularFileExists(depObjectFilePath));
QVERIFY(regularFileExists(depLibFilePath));
- for (const QString &symLink : qAsConst(symlinks))
+ for (const QString &symLink : std::as_const(symlinks))
QVERIFY2(symlinkExists(symLink), qPrintable(symLink));
}
@@ -1961,6 +1961,42 @@ void TestBlackbox::conanfileProbe()
QCOMPARE(actualResults, expectedResults);
}
+void TestBlackbox::conflictingPropertyValues_data()
+{
+ QTest::addColumn<bool>("overrideInProduct");
+ QTest::newRow("don't override in product") << false;
+ QTest::newRow("override in product") << true;
+}
+
+void TestBlackbox::conflictingPropertyValues()
+{
+ QFETCH(bool, overrideInProduct);
+
+ QDir::setCurrent(testDataDir + "/conflicting-property-values");
+ if (overrideInProduct)
+ REPLACE_IN_FILE("conflicting-property-values.qbs", "// low.prop: name", "low.prop: name");
+ else
+ REPLACE_IN_FILE("conflicting-property-values.qbs", "low.prop: name", "// low.prop: name");
+ WAIT_FOR_NEW_TIMESTAMP();
+ QCOMPARE(runQbs(QString("resolve")), 0);
+ if (overrideInProduct) {
+ // Binding in product itself overrides everything else, module-level conflicts
+ // are irrelevant.
+ QVERIFY2(m_qbsStdout.contains("final prop value: toplevel"), m_qbsStdout.constData());
+ QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
+ } else {
+ // Only the conflicts in the highest-level modules are reported, lower-level conflicts
+ // are irrelevant.
+ // prop2 does not cause a conflict, because the values are the same.
+ QVERIFY2(m_qbsStdout.contains("final prop value: highest"), m_qbsStdout.constData());
+ QVERIFY2(m_qbsStderr.contains("Conflicting scalar values for property 'prop'"),
+ m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.count("values.qbs") == 2, m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.contains("values.qbs:20:23"), m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.contains("values.qbs:30:23"), m_qbsStderr.constData());
+ }
+}
+
void TestBlackbox::cpuFeatures()
{
QDir::setCurrent(testDataDir + "/cpu-features");
@@ -1992,6 +2028,13 @@ void TestBlackbox::cpuFeatures()
}
}
+void TestBlackbox::dateProperty()
+{
+ QDir::setCurrent(testDataDir + "/date-property");
+ QCOMPARE(runQbs(), 0);
+ QVERIFY2(m_qbsStdout.contains("The stored date was 1999-12-31"), m_qbsStdout.constData());
+}
+
void TestBlackbox::renameDependency()
{
QDir::setCurrent(testDataDir + "/renameDependency");
@@ -3395,23 +3438,6 @@ void TestBlackbox::probeInExportedModule()
QVERIFY2(m_qbsStdout.contains("listProp: myother,my"), m_qbsStdout.constData());
}
-void TestBlackbox::probeInModuleProvider()
-{
- QDir::setCurrent(testDataDir + "/probe-in-module-provider");
-
- QbsRunParameters params;
- params.command = "build";
- params.arguments << "--force-probe-execution";
- QCOMPARE(runQbs(params), 0);
- QVERIFY2(m_qbsStdout.contains("Running probe"), m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.boolProp: true"), m_qbsStdout);
- WAIT_FOR_NEW_TIMESTAMP();
- touch("probe-in-module-provider.qbs");
- QCOMPARE(runQbs(), 0);
- QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.boolProp: true"), m_qbsStdout);
- QVERIFY2(!m_qbsStdout.contains("Running probe"), m_qbsStdout);
-}
-
void TestBlackbox::probesAndArrayProperties()
{
QDir::setCurrent(testDataDir + "/probes-and-array-properties");
@@ -3447,7 +3473,7 @@ void TestBlackbox::propertyAssignmentInFailedModule()
QVERIFY(runQbs(failParams) != 0);
QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.doFail:true"))), 0);
QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData());
- QEXPECT_FAIL(nullptr, "circular dependency between module merging and validation", Continue);
+ failParams.expectFailure = false;
QCOMPARE(runQbs(failParams), 0);
}
@@ -4400,7 +4426,7 @@ void TestBlackbox::installPackage()
cleanOutputLines.push_back(trimmedLine);
}
QCOMPARE(cleanOutputLines.size(), 3);
- for (const QByteArray &line : qAsConst(cleanOutputLines)) {
+ for (const QByteArray &line : std::as_const(cleanOutputLines)) {
QVERIFY2(line.contains("public_tool") || line.contains("mylib") || line.contains("lib.h"),
line.constData());
}
@@ -4477,7 +4503,7 @@ void TestBlackbox::invalidLibraryNames()
QbsRunParameters params(QStringList("project.valueIndex:" + index));
params.expectFailure = !success;
QCOMPARE(runQbs(params) == 0, success);
- for (const QString &diag : qAsConst(diagnostics))
+ for (const QString &diag : std::as_const(diagnostics))
QVERIFY2(m_qbsStderr.contains(diag.toLocal8Bit()), m_qbsStderr.constData());
}
@@ -5574,11 +5600,7 @@ void TestBlackbox::propertyPrecedence()
switchProfileContents(profile.p, s.get(), false);
switchFileContents(nonleafFile, true);
QCOMPARE(runQbs(resolveParams), 0);
- QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at")
- && m_qbsStderr.contains("nonleaf.qbs:4:22")
- && m_qbsStderr.contains("dep.qbs:6:26"),
- m_qbsStderr.constData());
- QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); QCOMPARE(runQbs(params), 0);
QVERIFY2(m_qbsStdout.contains("scalar prop: export\n")
&& m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"leaf\"]\n"),
m_qbsStdout.constData());
@@ -5586,10 +5608,7 @@ void TestBlackbox::propertyPrecedence()
// Case 8: [cmdline=0,prod=0,export=1,nonleaf=1,profile=1]
switchProfileContents(profile.p, s.get(), true);
QCOMPARE(runQbs(resolveParams), 0);
- QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at")
- && m_qbsStderr.contains("nonleaf.qbs:4:22")
- && m_qbsStderr.contains("dep.qbs:6:26"),
- m_qbsStderr.constData());
+ QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData());
QCOMPARE(runQbs(params), 0);
QVERIFY2(m_qbsStdout.contains("scalar prop: export\n")
&& m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"profile\"]\n"),
@@ -5977,19 +5996,6 @@ void TestBlackbox::protobufLibraryInstall()
QFileInfo::exists(installRootInclude + "/hello/world.pb.h"));
}
-// Tests whether it is possible to set providers properties in a Product or from command-line
-void TestBlackbox::providersProperties()
-{
- QDir::setCurrent(testDataDir + "/providers-properties");
-
- QbsRunParameters params("build");
- params.arguments = QStringList("moduleProviders.provider_b.someProp: \"first,second\"");
- QCOMPARE(runQbs(params), 0);
- QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.listProp: [\"someValue\"]"), m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(
- "p.qbsothermodule.listProp: [\"first\",\"second\"]"), m_qbsStdout);
-}
-
void TestBlackbox::pseudoMultiplexing()
{
// This is "pseudo-multiplexing" on all platforms that initialize qbs.architectures
@@ -6163,162 +6169,6 @@ void TestBlackbox::qbsConfigAddProfile_data()
<< QString("Profile properties must be key/value pairs");
}
-// checks that we can set qbs module properties in providers and provider cache works corectly
-void TestBlackbox::qbsModulePropertiesInProviders()
-{
- QDir::setCurrent(testDataDir + "/qbs-module-properties-in-providers");
-
- QbsRunParameters params("resolve");
-
- QCOMPARE(runQbs(params), 0);
-
- // We have 2 products in 2 configurations, but second product should use the cached value
- // so we should have only 2 copies of the module, not 4.
- QCOMPARE(m_qbsStdout.count("Running setup script for qbsmetatestmodule"), 2);
-
- // Check that products get correct values from modules
- QVERIFY2(m_qbsStdout.contains(("product1.qbsmetatestmodule.prop: sysroot1")), m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(("product1.qbsmetatestmodule.prop: sysroot2")), m_qbsStdout);
-
- QVERIFY2(m_qbsStdout.contains(("product2.qbsmetatestmodule.prop: sysroot1")), m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(("product2.qbsmetatestmodule.prop: sysroot2")), m_qbsStdout);
-}
-
-// Tests whether it is possible to set qbsModuleProviders in Product and Project items
-// and that the order of providers results in correct priority
-void TestBlackbox::qbsModuleProviders()
-{
- QFETCH(QStringList, arguments);
- QFETCH(QString, firstProp);
- QFETCH(QString, secondProp);
-
- QDir::setCurrent(testDataDir + "/qbs-module-providers");
-
- QbsRunParameters params("resolve");
- params.arguments = arguments;
- QCOMPARE(runQbs(params), 0);
- QVERIFY2(m_qbsStdout.contains(("p1.qbsmetatestmodule.prop: " + firstProp).toUtf8()),
- m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(("p1.qbsothermodule.prop: " + secondProp).toUtf8()),
- m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(("p2.qbsmetatestmodule.prop: " + firstProp).toUtf8()),
- m_qbsStdout);
- QVERIFY2(m_qbsStdout.contains(("p2.qbsothermodule.prop: " + secondProp).toUtf8()),
- m_qbsStdout);
-}
-
-void TestBlackbox::qbsModuleProviders_data()
-{
- QTest::addColumn<QStringList>("arguments");
- QTest::addColumn<QString>("firstProp");
- QTest::addColumn<QString>("secondProp");
-
- QTest::newRow("default") << QStringList() << "from_provider_a" << "undefined";
- QTest::newRow("override")
- << QStringList("projects.project.qbsModuleProviders:provider_b")
- << "from_provider_b"
- << "from_provider_b";
- QTest::newRow("override list a")
- << QStringList("projects.project.qbsModuleProviders:provider_a,provider_b")
- << "from_provider_a"
- << "from_provider_b";
- QTest::newRow("override list b")
- << QStringList("projects.project.qbsModuleProviders:provider_b,provider_a")
- << "from_provider_b"
- << "from_provider_b";
-}
-
-// Tests possible use-cases how to override providers from command-line
-void TestBlackbox::qbsModuleProvidersCliOverride()
-{
- QFETCH(QStringList, arguments);
- QFETCH(QString, propertyValue);
-
- QDir::setCurrent(testDataDir + "/qbs-module-providers-cli-override");
-
- QbsRunParameters params("resolve");
- params.arguments = arguments;
- QCOMPARE(runQbs(params), 0);
- QVERIFY2(m_qbsStdout.contains(("qbsmetatestmodule.prop: " + propertyValue).toUtf8()),
- m_qbsStdout);
-}
-
-void TestBlackbox::qbsModuleProvidersCliOverride_data()
-{
- QTest::addColumn<QStringList>("arguments");
- QTest::addColumn<QString>("propertyValue");
-
- QTest::newRow("default") << QStringList() << "undefined";
- QTest::newRow("project-wide")
- << QStringList("project.qbsModuleProviders:provider_a")
- << "from_provider_a";
- QTest::newRow("concrete project")
- << QStringList("projects.innerProject.qbsModuleProviders:provider_a")
- << "from_provider_a";
- QTest::newRow("concrete product")
- << QStringList("products.product.qbsModuleProviders:provider_a")
- << "from_provider_a";
- QTest::newRow("concrete project override project-wide")
- << QStringList({
- "project.qbsModuleProviders:provider_a",
- "projects.innerProject.qbsModuleProviders:provider_b"})
- << "from_provider_b";
- QTest::newRow("concrete product override project-wide")
- << QStringList({
- "project.qbsModuleProviders:provider_a",
- "products.product.qbsModuleProviders:provider_b"})
- << "from_provider_b";
-}
-
-// Tests whether scoped providers can be used as named, i.e. new provider machinery
-// is compatible with the old one
-void TestBlackbox::qbsModuleProvidersCompatibility()
-{
- QFETCH(QStringList, arguments);
- QFETCH(QString, propertyValue);
-
- QDir::setCurrent(testDataDir + "/qbs-module-providers-compatibility");
-
- QbsRunParameters params("resolve");
- params.arguments = arguments;
- QCOMPARE(runQbs(params), 0);
- QVERIFY2(m_qbsStdout.contains(("qbsmetatestmodule.prop: " + propertyValue).toUtf8()),
- m_qbsStdout);
-}
-
-void TestBlackbox::qbsModuleProvidersCompatibility_data()
-{
- QTest::addColumn<QStringList>("arguments");
- QTest::addColumn<QString>("propertyValue");
-
- QTest::newRow("default") << QStringList() << "from_scoped_provider";
- QTest::newRow("scoped by name") << QStringList("project.qbsModuleProviders:qbsmetatestmodule") << "from_scoped_provider";
- QTest::newRow("named") << QStringList("project.qbsModuleProviders:named_provider") << "from_named_provider";
-}
-
-void TestBlackbox::qbspkgconfigModuleProvider()
-{
- QDir::setCurrent(testDataDir + "/qbspkgconfig-module-provider/libs");
- rmDirR(relativeBuildDir());
-
- const auto commonParams = QbsRunParameters(QStringLiteral("install"), {
- QStringLiteral("--install-root"),
- QStringLiteral("install-root")
- });
- auto dynamicParams = commonParams;
- dynamicParams.arguments << "config:library" << "projects.libs.isBundle:false";
- QCOMPARE(runQbs(dynamicParams), 0);
-
- QDir::setCurrent(testDataDir + "/qbspkgconfig-module-provider");
- rmDirR(relativeBuildDir());
-
- const auto sysroot = testDataDir + "/qbspkgconfig-module-provider/libs/install-root";
-
- QbsRunParameters params;
- params.arguments << "moduleProviders.qbspkgconfig.sysroot:" + sysroot;
- QCOMPARE(runQbs(params), 0);
-}
-
static QJsonObject getNextSessionPacket(QProcess &session, QByteArray &data)
{
int totalSize = -1;
@@ -7490,7 +7340,7 @@ static bool haveMakeNsis()
QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH")
.split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
- for (const QString &key : qAsConst(regKeys)) {
+ for (const QString &key : std::as_const(regKeys)) {
QSettings settings(key, QSettings::NativeFormat);
QString str = settings.value(QStringLiteral(".")).toString();
if (!str.isEmpty())
@@ -7498,7 +7348,7 @@ static bool haveMakeNsis()
}
bool haveMakeNsis = false;
- for (const QString &path : qAsConst(paths)) {
+ for (const QString &path : std::as_const(paths)) {
if (regularFileExists(QDir::fromNativeSeparators(path) +
HostOsInfo::appendExecutableSuffix(QStringLiteral("/makensis")))) {
haveMakeNsis = true;
@@ -8024,112 +7874,6 @@ void TestBlackbox::maximumCxxLanguageVersion()
m_qbsStdout.constData());
}
-void TestBlackbox::moduleProviders()
-{
- QDir::setCurrent(testDataDir + "/module-providers");
-
- // Resolving in dry-run mode must not leave any data behind.
- QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0);
- if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
- QSKIP("Cannot run binaries in cross-compiled build");
- QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2);
- QVERIFY(!QFile::exists(relativeBuildDir()));
-
- // Initial build.
- QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
- QVERIFY(QFile::exists(relativeBuildDir()));
- QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2);
- QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
- QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app1"), m_qbsStdout.constData());
- QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
- QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData());
- QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app2"), m_qbsStdout.constData());
-
- // Rebuild with overridden module provider config. The output for product 2 must change,
- // but no setup script must be re-run, because both config values have already been
- // handled in the first run.
- const QStringList resolveArgs("moduleProviders.mygenerator.chooseLettersFrom:beginning");
- QCOMPARE(runQbs(QbsRunParameters("resolve", resolveArgs)), 0);
- QVERIFY2(!m_qbsStdout.contains("Running setup script"), m_qbsStdout.constData());
- QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
- QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
- QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
- QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
-
- // Forcing Probe execution triggers a re-run of the setup script. But only once,
- // because the module provider config is the same now.
- QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList(resolveArgs)
- << "--force-probe-execution")), 0);
- QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1);
- QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
- QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
- QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
- QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
-
- // Now re-run without the module provider config override. Again, the setup script must
- // run once, for the config value that was not present in the last run.
- QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
- QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1);
- QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
- QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
- QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
- QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData());
-}
-
-void TestBlackbox::fallbackModuleProvider_data()
-{
- 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;
-}
-
-void TestBlackbox::fallbackModuleProvider()
-{
- 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);
-}
-
void TestBlackbox::minimumSystemVersion()
{
rmDirR(relativeBuildDir());
@@ -8250,7 +7994,7 @@ void TestBlackbox::missingBuildGraph()
QbsRunParameters params;
params.expectFailure = true;
params.arguments << QLatin1String("config:") + actualConfigName;
- for (const QString &command : qAsConst(commands)) {
+ for (const QString &command : std::as_const(commands)) {
params.command = command;
QVERIFY2(runQbs(params) != 0, qPrintable(command));
const QString expectedErrorMessage = QString("Build graph not found for "
diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h
index 8f9bfc984..4f8e898aa 100644
--- a/tests/auto/blackbox/tst_blackbox.h
+++ b/tests/auto/blackbox/tst_blackbox.h
@@ -90,7 +90,10 @@ private slots:
void cxxLanguageVersion_data();
void conanfileProbe_data();
void conanfileProbe();
+ void conflictingPropertyValues_data();
+ void conflictingPropertyValues();
void cpuFeatures();
+ void dateProperty();
void dependenciesProperty();
void dependencyScanningLoop();
void deprecatedProperty();
@@ -195,9 +198,6 @@ private slots:
void makefileGenerator();
void maximumCLanguageVersion();
void maximumCxxLanguageVersion();
- void moduleProviders();
- void fallbackModuleProvider_data();
- void fallbackModuleProvider();
void minimumSystemVersion();
void minimumSystemVersion_data();
void missingBuildGraph();
@@ -242,7 +242,6 @@ private slots:
void probeProperties();
void probesAndShadowProducts();
void probeInExportedModule();
- void probeInModuleProvider();
void probesAndArrayProperties();
void probesInNestedModules();
void productDependenciesByType();
@@ -258,19 +257,10 @@ private slots:
void protobuf_data();
void protobuf();
void protobufLibraryInstall();
- void providersProperties();
void pseudoMultiplexing();
void qbsConfig();
void qbsConfigAddProfile();
void qbsConfigAddProfile_data();
- void qbsModulePropertiesInProviders();
- void qbsModuleProviders();
- void qbsModuleProviders_data();
- void qbsModuleProvidersCliOverride();
- void qbsModuleProvidersCliOverride_data();
- void qbsModuleProvidersCompatibility();
- void qbsModuleProvidersCompatibility_data();
- void qbspkgconfigModuleProvider();
void qbsSession();
void qbsVersion();
void qtBug51237();
diff --git a/tests/auto/blackbox/tst_blackboxandroid.cpp b/tests/auto/blackbox/tst_blackboxandroid.cpp
index 06027a100..a74d9415d 100644
--- a/tests/auto/blackbox/tst_blackboxandroid.cpp
+++ b/tests/auto/blackbox/tst_blackboxandroid.cpp
@@ -131,7 +131,7 @@ void TestBlackboxAndroid::android()
buildParams.buildDirectory = buildSubDir;
buildParams.profile = p.name();
QCOMPARE(runQbs(buildParams), 0);
- for (const QString &productName : qAsConst(productNames)) {
+ for (const QString &productName : std::as_const(productNames)) {
const QByteArray tag(QTest::currentDataTag());
QCOMPARE(m_qbsStdout.count("generating BuildConfig.java"),
isIncrementalBuild ? 0 : productNames.size());
@@ -262,7 +262,7 @@ void TestBlackboxAndroid::android_data()
QByteArray base( aabPackage ? "base/" : "");
for (const QByteArray &entry : lst) {
if (entry.contains(archPlaceHolder)) {
- for (const QByteArray &arch : qAsConst(archs))
+ for (const QByteArray &arch : std::as_const(archs))
result << (base + QByteArray(entry).replace(archPlaceHolder, arch));
} else {
result << (base + entry);
@@ -309,7 +309,6 @@ void TestBlackboxAndroid::android_data()
return QString("modules.Android.sdk.dexCompilerName:") + (enableD8 ? "d8" : "dx");
};
bool enableD8 = true;
-
auto qtAppExpectedFiles = [&](bool generateAab, bool enableAapt2, bool codeSign = true,
QString keyAlias="androiddebugkey") {
QByteArrayList expectedFile;
@@ -357,6 +356,11 @@ void TestBlackboxAndroid::android_data()
"lib/${ARCH}/libplugins_imageformats_qtiff_${ARCH}.so",
"lib/${ARCH}/libplugins_imageformats_qwbmp_${ARCH}.so",
"lib/${ARCH}/libplugins_imageformats_qwebp_${ARCH}.so"}, generateAab);
+ if (version >= qbs::Version(6, 5))
+ expectedFile << expandArchs(ndkArchsForQt, {
+ "lib/${ARCH}/libQt6Svg_${ARCH}.so",
+ "lib/${ARCH}/libplugins_iconengines_qsvgicon_${ARCH}.so",
+ "lib/${ARCH}/libplugins_imageformats_qsvg_${ARCH}.so"}, generateAab);
if (!enableAapt2 && version < qbs::Version(6, 0))
expectedFile << "res/layout/splash.xml";
return expectedFile;
@@ -613,13 +617,25 @@ void TestBlackboxAndroid::android_data()
"lib/${ARCH}/libplugins_imageformats_qtiff_${ARCH}.so",
"lib/${ARCH}/libplugins_imageformats_qwbmp_${ARCH}.so",
"lib/${ARCH}/libplugins_imageformats_qwebp_${ARCH}.so"}, generateAab);
- if (version >= qbs::Version(6, 0))
+ if (version >= qbs::Version(6, 5))
+ expectedFile << expandArchs(ndkArchsForQt, {
+ "lib/${ARCH}/libQt6Svg_${ARCH}.so",
+ "lib/${ARCH}/libplugins_iconengines_qsvgicon_${ARCH}.so",
+ "lib/${ARCH}/libplugins_imageformats_qsvg_${ARCH}.so"}, generateAab);
+ if (version >= qbs::Version(6, 0)) {
expectedFile << expandArchs(ndkArchsForQt, {
"lib/${ARCH}/libQt6OpenGL_${ARCH}.so",
"lib/${ARCH}/libqml_QtQml_Models_modelsplugin_${ARCH}.so",
"lib/${ARCH}/libqml_QtQml_WorkerScript_workerscriptplugin_${ARCH}.so",
- "lib/${ARCH}/libqml_QtQml_qmlplugin_${ARCH}.so",
"lib/${ARCH}/libqml_QtQuick_qtquick2plugin_${ARCH}.so"}, generateAab);
+ if (version >= qbs::Version(6, 5))
+ expectedFile << expandArchs(ndkArchsForQt, {
+ "lib/${ARCH}/libqml_QtQml_Base_qmlplugin_${ARCH}.so",
+ "lib/${ARCH}/libqml_QtQml_qmlmetaplugin_${ARCH}.so"}, generateAab);
+ else
+ expectedFile << expandArchs(ndkArchsForQt, {
+ "lib/${ARCH}/libqml_QtQml_qmlplugin_${ARCH}.so"}, generateAab);
+ }
if (version >= qbs::Version(6, 2))
expectedFile << expandArchs(ndkArchsForQt, {
"lib/${ARCH}/libplugins_networkinformation_qandroidnetworkinformation_${ARCH}.so",
@@ -627,6 +643,7 @@ void TestBlackboxAndroid::android_data()
"lib/${ARCH}/libplugins_tls_qopensslbackend_${ARCH}.so",
"lib/${ARCH}/libqml_QtQuick_Window_quickwindowplugin_${ARCH}.so",
}, generateAab);
+
if (version >= qbs::Version(6, 0) && version < qbs::Version(6, 3)) {
expectedFile << expandArchs(ndkArchsForQt, {
"lib/${ARCH}/libQt6QuickControls2Impl_${ARCH}.so",
@@ -669,8 +686,8 @@ void TestBlackboxAndroid::android_data()
"lib/${ARCH}/libqml_QtQuick_Dialogs_qtquickdialogsplugin_${ARCH}.so",
"lib/${ARCH}/libqml_QtQuick_Dialogs_quickimpl_qtquickdialogs2quickimplplugin_${ARCH}.so"},
generateAab);
- else
- expectedFile << expandArchs(ndkArchsForQt, {
+ else
+ expectedFile << expandArchs(ndkArchsForQt, {
"lib/${ARCH}/libqml_QtQuick_Window_quickwindow_${ARCH}.so",
"lib/${ARCH}/libqml_QtQuick_tooling_quicktooling_${ARCH}.so"}, generateAab);
}
@@ -774,13 +791,25 @@ void TestBlackboxAndroid::android_data()
"lib/${ARCH}/libplugins_imageformats_qtiff_${ARCH}.so",
"lib/${ARCH}/libplugins_imageformats_qwbmp_${ARCH}.so",
"lib/${ARCH}/libplugins_imageformats_qwebp_${ARCH}.so"}, generateAab);
- if (version >= qbs::Version(6, 0))
+ if (version >= qbs::Version(6, 5))
+ expectedFile << expandArchs(ndkArchsForQt, {
+ "lib/${ARCH}/libQt6Svg_${ARCH}.so",
+ "lib/${ARCH}/libplugins_iconengines_qsvgicon_${ARCH}.so",
+ "lib/${ARCH}/libplugins_imageformats_qsvg_${ARCH}.so"}, generateAab);
+ if (version >= qbs::Version(6, 0)) {
expectedFile << expandArchs(ndkArchsForQt, {
"lib/${ARCH}/libQt6OpenGL_${ARCH}.so",
"lib/${ARCH}/libqml_QtQml_Models_modelsplugin_${ARCH}.so",
"lib/${ARCH}/libqml_QtQml_WorkerScript_workerscriptplugin_${ARCH}.so",
- "lib/${ARCH}/libqml_QtQml_qmlplugin_${ARCH}.so",
"lib/${ARCH}/libqml_QtQuick_qtquick2plugin_${ARCH}.so"}, generateAab);
+ if (version >= qbs::Version(6, 5))
+ expectedFile << expandArchs(ndkArchsForQt, {
+ "lib/${ARCH}/libqml_QtQml_Base_qmlplugin_${ARCH}.so",
+ "lib/${ARCH}/libqml_QtQml_qmlmetaplugin_${ARCH}.so"}, generateAab);
+ else
+ expectedFile << expandArchs(ndkArchsForQt, {
+ "lib/${ARCH}/libqml_QtQml_qmlplugin_${ARCH}.so"}, generateAab);
+ }
if (version >= qbs::Version(6, 2))
expectedFile << expandArchs(ndkArchsForQt, {
"lib/${ARCH}/libplugins_networkinformation_qandroidnetworkinformation_${ARCH}.so",
@@ -967,14 +996,14 @@ void TestBlackboxAndroid::android_data()
<< QStringList{aaptVersion(enableAapt2), packageType(generateAab)}
<< enableAapt2 << generateAab << isIncrementalBuild << enableD8;
enableAapt2 = true;
- QTest::newRow("aidl") << "aidl" << QStringList("io.qbs.aidltest")
+ QTest::newRow("aidl aapt2") << "aidl" << QStringList("io.qbs.aidltest")
<< (QList<QByteArrayList>() << (QByteArrayList()
<< commonFiles(generateAab)
<< "resources.arsc"))
<< QStringList{aaptVersion(enableAapt2), packageType(generateAab)}
<< enableAapt2 << generateAab << isIncrementalBuild << enableD8;
generateAab = true;
- QTest::newRow("aidl") << "aidl" << QStringList("io.qbs.aidltest")
+ QTest::newRow("aidl aab") << "aidl" << QStringList("io.qbs.aidltest")
<< (QList<QByteArrayList>() << (QByteArrayList()
<< commonFiles(generateAab)
<< "base/resources.pb"))
@@ -1031,6 +1060,7 @@ void TestBlackboxAndroid::android_data()
<< QStringList{aaptVersion(enableAapt2), packageType(generateAab),
dexCompilerVersion(enableD8)}
<< enableAapt2 << generateAab << isIncrementalBuild << enableD8;
+
enableAapt2 = false;
generateAab = false;
auto expectedFiles1 = [&](bool generateAab) {
@@ -1054,7 +1084,6 @@ void TestBlackboxAndroid::android_data()
cxxLibPath("libstlport_shared.so", false)}, generateAab);
return expectedFile;
};
-
QTest::newRow("multiple apks")
<< "multiple-apks-per-project"
<< (QStringList() << "twolibs1" << "twolibs2")
diff --git a/tests/auto/blackbox/tst_blackboxapple.cpp b/tests/auto/blackbox/tst_blackboxapple.cpp
index 5de371792..01446c815 100644
--- a/tests/auto/blackbox/tst_blackboxapple.cpp
+++ b/tests/auto/blackbox/tst_blackboxapple.cpp
@@ -684,6 +684,23 @@ void TestBlackboxApple::bundleStructure_data()
QTest::newRow("G") << "G" << "com.apple.product-type.in-app-purchase-content";
}
+void TestBlackboxApple::byteArrayInfoPlist()
+{
+ QDir::setCurrent(testDataDir + "/byteArrayInfoPlist");
+
+ QCOMPARE(runQbs(), 0);
+
+ const auto infoPlistPath = getInfoPlistPath(
+ relativeProductBuildDir("byteArrayInfoPlist") + "/byteArrayInfoPlist.app");
+ QVERIFY(QFile::exists(infoPlistPath));
+ const auto outFilePath =
+ relativeProductBuildDir("byteArrayInfoPlist") + "/bytearrayInfoPlist-Info.plist.out";
+ QFile file(outFilePath);
+ QVERIFY(file.exists());
+ QVERIFY(file.open(QIODevice::ReadOnly));
+ QCOMPARE(file.readAll(), "The data value");
+}
+
void TestBlackboxApple::codesign()
{
QFETCH(bool, isBundle);
diff --git a/tests/auto/blackbox/tst_blackboxapple.h b/tests/auto/blackbox/tst_blackboxapple.h
index 32eee2432..9c329e961 100644
--- a/tests/auto/blackbox/tst_blackboxapple.h
+++ b/tests/auto/blackbox/tst_blackboxapple.h
@@ -55,6 +55,7 @@ private slots:
void assetCatalogsMultiple();
void bundleStructure();
void bundleStructure_data();
+ void byteArrayInfoPlist();
void codesign();
void codesign_data();
void deploymentTarget();
diff --git a/tests/auto/blackbox/tst_blackboxjava.cpp b/tests/auto/blackbox/tst_blackboxjava.cpp
index 9495ab1b7..05bd99078 100644
--- a/tests/auto/blackbox/tst_blackboxjava.cpp
+++ b/tests/auto/blackbox/tst_blackboxjava.cpp
@@ -96,7 +96,7 @@ void TestBlackboxJava::java()
// Now check whether we correctly predicted the class file output paths.
QCOMPARE(runQbs(QbsRunParameters("clean")), 0);
- for (const QString &classFile : qAsConst(classFiles1)) {
+ for (const QString &classFile : std::as_const(classFiles1)) {
QVERIFY2(!regularFileExists(classFile), qPrintable(classFile));
}
diff --git a/tests/auto/blackbox/tst_blackboxproviders.cpp b/tests/auto/blackbox/tst_blackboxproviders.cpp
new file mode 100644
index 000000000..ceb413510
--- /dev/null
+++ b/tests/auto/blackbox/tst_blackboxproviders.cpp
@@ -0,0 +1,368 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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_blackboxproviders.h"
+
+#include "../shared.h"
+
+// #include <tools/hostosinfo.h>
+// #include <tools/profile.h>
+// #include <tools/qttools.h>
+
+// #include <QtCore/qdir.h>
+// #include <QtCore/qregularexpression.h>
+
+// using qbs::Internal::HostOsInfo;
+// using qbs::Profile;
+
+#define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir)
+
+TestBlackboxProviders::TestBlackboxProviders()
+ : TestBlackboxBase(SRCDIR "/testdata-providers", "blackbox-providers")
+{
+}
+
+void TestBlackboxProviders::fallbackModuleProvider_data()
+{
+ 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;
+}
+
+void TestBlackboxProviders::fallbackModuleProvider()
+{
+ 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);
+}
+
+void TestBlackboxProviders::moduleProviders()
+{
+ QDir::setCurrent(testDataDir + "/module-providers");
+
+ // Resolving in dry-run mode must not leave any data behind.
+ QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0);
+ if (m_qbsStdout.contains("targetPlatform differs from hostPlatform"))
+ QSKIP("Cannot run binaries in cross-compiled build");
+ QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2);
+ QVERIFY(!QFile::exists(relativeBuildDir()));
+
+ // Initial build.
+ QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
+ QVERIFY(QFile::exists(relativeBuildDir()));
+ QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2);
+ QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
+ QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app1"), m_qbsStdout.constData());
+ QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
+ QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData());
+ QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app2"), m_qbsStdout.constData());
+
+ // Rebuild with overridden module provider config. The output for product 2 must change,
+ // but no setup script must be re-run, because both config values have already been
+ // handled in the first run.
+ const QStringList resolveArgs("moduleProviders.mygenerator.chooseLettersFrom:beginning");
+ QCOMPARE(runQbs(QbsRunParameters("resolve", resolveArgs)), 0);
+ QVERIFY2(!m_qbsStdout.contains("Running setup script"), m_qbsStdout.constData());
+ QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
+ QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
+ QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
+ QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
+
+ // Forcing Probe execution triggers a re-run of the setup script. But only once,
+ // because the module provider config is the same now.
+ QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList(resolveArgs)
+ << "--force-probe-execution")), 0);
+ QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1);
+ QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
+ QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
+ QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
+ QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
+
+ // Now re-run without the module provider config override. Again, the setup script must
+ // run once, for the config value that was not present in the last run.
+ QCOMPARE(runQbs(QbsRunParameters("resolve")), 0);
+ QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1);
+ QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0);
+ QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData());
+ QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0);
+ QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData());
+}
+
+// Checks regression - when loading 2 modules from the same provider, the second module should
+// come from provider cache
+void TestBlackboxProviders::moduleProvidersCache()
+{
+ QDir::setCurrent(testDataDir + "/module-providers-cache");
+
+ QbsRunParameters params("resolve", {"-v"});
+ QCOMPARE(runQbs(params), 0);
+ const auto qbsmetatestmoduleMessage = "Re-checking for module \"qbsmetatestmodule\" with "
+ "newly added search paths from module provider";
+ const auto qbsothermoduleMessage = "Re-checking for module \"qbsothermodule\" with "
+ "newly added search paths from module provider";
+ QCOMPARE(m_qbsStderr.count(qbsmetatestmoduleMessage), 1);
+ QCOMPARE(m_qbsStderr.count(qbsothermoduleMessage), 1);
+ QCOMPARE(m_qbsStderr.count("Re-using provider \"provider_a\" from cache"), 1);
+
+ // We didn't change providers, so both modules should come from cache.
+ params.arguments << "project.dummyProp:value";
+ QCOMPARE(runQbs(params), 0);
+ QCOMPARE(m_qbsStderr.count(qbsmetatestmoduleMessage), 1);
+ QCOMPARE(m_qbsStderr.count(qbsothermoduleMessage), 1);
+ QCOMPARE(m_qbsStderr.count("Re-using provider \"provider_a\" from cache"), 2);
+}
+
+void TestBlackboxProviders::probeInModuleProvider()
+{
+ QDir::setCurrent(testDataDir + "/probe-in-module-provider");
+
+ QbsRunParameters params;
+ params.command = "build";
+ params.arguments << "--force-probe-execution";
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains("Running probe"), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.boolProp: true"), m_qbsStdout);
+ WAIT_FOR_NEW_TIMESTAMP();
+ touch("probe-in-module-provider.qbs");
+ QCOMPARE(runQbs(), 0);
+ QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.boolProp: true"), m_qbsStdout);
+ QVERIFY2(!m_qbsStdout.contains("Running probe"), m_qbsStdout);
+}
+
+// Tests whether it is possible to set providers properties in a Product or from command-line
+void TestBlackboxProviders::providersProperties()
+{
+ QDir::setCurrent(testDataDir + "/providers-properties");
+
+ QbsRunParameters params("build");
+ params.arguments = QStringList("moduleProviders.provider_b.someProp: \"first,second\"");
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains("p.qbsmetatestmodule.listProp: [\"someValue\"]"), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(
+ "p.qbsothermodule.listProp: [\"first\",\"second\"]"), m_qbsStdout);
+}
+
+// checks that we can set qbs module properties in providers and provider cache works corectly
+void TestBlackboxProviders::qbsModulePropertiesInProviders()
+{
+ QDir::setCurrent(testDataDir + "/qbs-module-properties-in-providers");
+
+ QbsRunParameters params("resolve");
+
+ QCOMPARE(runQbs(params), 0);
+
+ // We have 2 products in 2 configurations, but second product should use the cached value
+ // so we should have only 2 copies of the module, not 4.
+ QCOMPARE(m_qbsStdout.count("Running setup script for qbsmetatestmodule"), 2);
+
+ // Check that products get correct values from modules
+ QVERIFY2(m_qbsStdout.contains(("product1.qbsmetatestmodule.prop: /sysroot1")), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("product1.qbsmetatestmodule.prop: /sysroot2")), m_qbsStdout);
+
+ QVERIFY2(m_qbsStdout.contains(("product2.qbsmetatestmodule.prop: /sysroot1")), m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("product2.qbsmetatestmodule.prop: /sysroot2")), m_qbsStdout);
+}
+
+void TestBlackboxProviders::qbsModuleProviders_data()
+{
+ QTest::addColumn<QStringList>("arguments");
+ QTest::addColumn<QString>("firstProp");
+ QTest::addColumn<QString>("secondProp");
+
+ QTest::newRow("default") << QStringList() << "from_provider_a" << "undefined";
+ QTest::newRow("override")
+ << QStringList("projects.project.qbsModuleProviders:provider_b")
+ << "from_provider_b"
+ << "from_provider_b";
+ QTest::newRow("override list a")
+ << QStringList("projects.project.qbsModuleProviders:provider_a,provider_b")
+ << "from_provider_a"
+ << "from_provider_b";
+ QTest::newRow("override list b")
+ << QStringList("projects.project.qbsModuleProviders:provider_b,provider_a")
+ << "from_provider_b"
+ << "from_provider_b";
+}
+
+// Tests whether it is possible to set qbsModuleProviders in Product and Project items
+// and that the order of providers results in correct priority
+void TestBlackboxProviders::qbsModuleProviders()
+{
+ QFETCH(QStringList, arguments);
+ QFETCH(QString, firstProp);
+ QFETCH(QString, secondProp);
+
+ QDir::setCurrent(testDataDir + "/qbs-module-providers");
+
+ QbsRunParameters params("resolve");
+ params.arguments = arguments;
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains(("p1.qbsmetatestmodule.prop: " + firstProp).toUtf8()),
+ m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("p1.qbsothermodule.prop: " + secondProp).toUtf8()),
+ m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("p2.qbsmetatestmodule.prop: " + firstProp).toUtf8()),
+ m_qbsStdout);
+ QVERIFY2(m_qbsStdout.contains(("p2.qbsothermodule.prop: " + secondProp).toUtf8()),
+ m_qbsStdout);
+}
+
+void TestBlackboxProviders::qbsModuleProvidersCliOverride_data()
+{
+ QTest::addColumn<QStringList>("arguments");
+ QTest::addColumn<QString>("propertyValue");
+
+ QTest::newRow("default") << QStringList() << "undefined";
+ QTest::newRow("project-wide")
+ << QStringList("project.qbsModuleProviders:provider_a")
+ << "from_provider_a";
+ QTest::newRow("concrete project")
+ << QStringList("projects.innerProject.qbsModuleProviders:provider_a")
+ << "from_provider_a";
+ QTest::newRow("concrete product")
+ << QStringList("products.product.qbsModuleProviders:provider_a")
+ << "from_provider_a";
+ QTest::newRow("concrete project override project-wide")
+ << QStringList({
+ "project.qbsModuleProviders:provider_a",
+ "projects.innerProject.qbsModuleProviders:provider_b"})
+ << "from_provider_b";
+ QTest::newRow("concrete product override project-wide")
+ << QStringList({
+ "project.qbsModuleProviders:provider_a",
+ "products.product.qbsModuleProviders:provider_b"})
+ << "from_provider_b";
+}
+
+// Tests possible use-cases how to override providers from command-line
+void TestBlackboxProviders::qbsModuleProvidersCliOverride()
+{
+ QFETCH(QStringList, arguments);
+ QFETCH(QString, propertyValue);
+
+ QDir::setCurrent(testDataDir + "/qbs-module-providers-cli-override");
+
+ QbsRunParameters params("resolve");
+ params.arguments = arguments;
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains(("qbsmetatestmodule.prop: " + propertyValue).toUtf8()),
+ m_qbsStdout);
+}
+
+void TestBlackboxProviders::qbsModuleProvidersCompatibility_data()
+{
+ QTest::addColumn<QStringList>("arguments");
+ QTest::addColumn<QString>("propertyValue");
+
+ QTest::newRow("default") << QStringList() << "from_scoped_provider";
+ QTest::newRow("scoped by name") << QStringList("project.qbsModuleProviders:qbsmetatestmodule") << "from_scoped_provider";
+ QTest::newRow("named") << QStringList("project.qbsModuleProviders:named_provider") << "from_named_provider";
+}
+
+// Tests whether scoped providers can be used as named, i.e. new provider machinery
+// is compatible with the old one
+void TestBlackboxProviders::qbsModuleProvidersCompatibility()
+{
+ QFETCH(QStringList, arguments);
+ QFETCH(QString, propertyValue);
+
+ QDir::setCurrent(testDataDir + "/qbs-module-providers-compatibility");
+
+ QbsRunParameters params("resolve");
+ params.arguments = arguments;
+ QCOMPARE(runQbs(params), 0);
+ QVERIFY2(m_qbsStdout.contains(("qbsmetatestmodule.prop: " + propertyValue).toUtf8()),
+ m_qbsStdout);
+}
+
+void TestBlackboxProviders::qbspkgconfigModuleProvider()
+{
+ QDir::setCurrent(testDataDir + "/qbspkgconfig-module-provider/libs");
+ rmDirR(relativeBuildDir());
+
+ const auto commonParams = QbsRunParameters(QStringLiteral("install"), {
+ QStringLiteral("--install-root"),
+ QStringLiteral("install-root")
+ });
+ auto dynamicParams = commonParams;
+ dynamicParams.arguments << "config:library" << "projects.libs.isBundle:false";
+ QCOMPARE(runQbs(dynamicParams), 0);
+
+ QDir::setCurrent(testDataDir + "/qbspkgconfig-module-provider");
+ rmDirR(relativeBuildDir());
+
+ const auto sysroot = testDataDir + "/qbspkgconfig-module-provider/libs/install-root";
+
+ QbsRunParameters params;
+ params.arguments << "moduleProviders.qbspkgconfig.sysroot:" + sysroot;
+ QCOMPARE(runQbs(params), 0);
+}
+
+QTEST_MAIN(TestBlackboxProviders)
diff --git a/tests/auto/blackbox/tst_blackboxproviders.h b/tests/auto/blackbox/tst_blackboxproviders.h
new file mode 100644
index 000000000..017cc1c5e
--- /dev/null
+++ b/tests/auto/blackbox/tst_blackboxproviders.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2023 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_BLACKBOXPROVIDERS_H
+#define TST_BLACKBOXPROVIDERS_H
+
+#include "tst_blackboxbase.h"
+
+class TestBlackboxProviders : public TestBlackboxBase
+{
+ Q_OBJECT
+
+public:
+ TestBlackboxProviders();
+
+private slots:
+ void fallbackModuleProvider_data();
+ void fallbackModuleProvider();
+ void moduleProviders();
+ void moduleProvidersCache();
+ void probeInModuleProvider();
+ void providersProperties();
+ void qbsModulePropertiesInProviders();
+ void qbsModuleProviders_data();
+ void qbsModuleProviders();
+ void qbsModuleProvidersCliOverride();
+ void qbsModuleProvidersCliOverride_data();
+ void qbsModuleProvidersCompatibility();
+ void qbsModuleProvidersCompatibility_data();
+ void qbspkgconfigModuleProvider();
+};
+
+#endif // TST_BLACKBOXPROVIDERS_H
diff --git a/tests/auto/blackbox/tst_blackboxwindows.cpp b/tests/auto/blackbox/tst_blackboxwindows.cpp
index 94257c062..1c487bc52 100644
--- a/tests/auto/blackbox/tst_blackboxwindows.cpp
+++ b/tests/auto/blackbox/tst_blackboxwindows.cpp
@@ -230,7 +230,7 @@ static bool haveWiX(const Profile &profile)
QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH")
.split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS);
- for (const QString &key : qAsConst(regKeys)) {
+ for (const QString &key : std::as_const(regKeys)) {
const QStringList versions = QSettings(key, QSettings::NativeFormat).childGroups();
for (const QString &version : versions) {
QSettings settings(key + version, QSettings::NativeFormat);
@@ -240,7 +240,7 @@ static bool haveWiX(const Profile &profile)
}
}
- for (const QString &path : qAsConst(paths)) {
+ for (const QString &path : std::as_const(paths)) {
if (regularFileExists(QDir::fromNativeSeparators(path) +
HostOsInfo::appendExecutableSuffix(QStringLiteral("/candle"))) &&
regularFileExists(QDir::fromNativeSeparators(path) +
diff --git a/tests/auto/language/testdata/erroneous/duplicate-multiplex-value.qbs b/tests/auto/language/testdata/duplicate-multiplex-value.qbs
index 24b246604..24b246604 100644
--- a/tests/auto/language/testdata/erroneous/duplicate-multiplex-value.qbs
+++ b/tests/auto/language/testdata/duplicate-multiplex-value.qbs
diff --git a/tests/auto/language/testdata/erroneous/duplicate-multiplex-value2.qbs b/tests/auto/language/testdata/duplicate-multiplex-value2.qbs
index d6c057a9e..d6c057a9e 100644
--- a/tests/auto/language/testdata/erroneous/duplicate-multiplex-value2.qbs
+++ b/tests/auto/language/testdata/duplicate-multiplex-value2.qbs
diff --git a/tests/auto/language/testdata/erroneous/module-depends-on-product.qbs b/tests/auto/language/testdata/module-depends-on-product.qbs
index a7db9e036..a7db9e036 100644
--- a/tests/auto/language/testdata/erroneous/module-depends-on-product.qbs
+++ b/tests/auto/language/testdata/module-depends-on-product.qbs
diff --git a/tests/auto/language/testdata/modulepropertiesingroups.qbs b/tests/auto/language/testdata/modulepropertiesingroups.qbs
index e3857bdf4..49f24c0ca 100644
--- a/tests/auto/language/testdata/modulepropertiesingroups.qbs
+++ b/tests/auto/language/testdata/modulepropertiesingroups.qbs
@@ -80,4 +80,13 @@ Project {
}
}
}
+
+ Product {
+ name: "module-property-in-group-condition"
+ Depends { name: "cpp" }
+ Group {
+ condition: qbs.architecture === "x86_64"
+ cpp.includePaths: "."
+ }
+ }
}
diff --git a/tests/auto/language/testdata/modules/broken/broken.qbs b/tests/auto/language/testdata/modules/broken/broken.qbs
index 302573bbf..a80547340 100644
--- a/tests/auto/language/testdata/modules/broken/broken.qbs
+++ b/tests/auto/language/testdata/modules/broken/broken.qbs
@@ -1,5 +1,3 @@
-import qbs // FIXME: Don't remove this import because then the test fails!
-
Module {
Probe {
id: theProbe
diff --git a/tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs b/tests/auto/language/testdata/modules/module-with-product-dependency/module-with-product-dependency.qbs
index 5781bd6de..5781bd6de 100644
--- a/tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs
+++ b/tests/auto/language/testdata/modules/module-with-product-dependency/module-with-product-dependency.qbs
diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp
index 9045442c9..b6dd78141 100644
--- a/tests/auto/language/tst_language.cpp
+++ b/tests/auto/language/tst_language.cpp
@@ -53,6 +53,7 @@
#include <language/propertymapinternal.h>
#include <language/scriptengine.h>
#include <language/value.h>
+#include <loader/projectresolver.h>
#include <parser/qmljslexer_p.h>
#include <parser/qmljsparser_p.h>
#include <tools/scripttools.h>
@@ -92,14 +93,9 @@ TestLanguage::TestLanguage(ILogSink *logSink, Settings *settings)
{
m_rand.seed(QTime::currentTime().msec());
qRegisterMetaType<QList<bool> >("QList<bool>");
- defaultParameters.setBuildRoot(m_tempDir.path() + "/buildroot");
- defaultParameters.setPropertyCheckingMode(ErrorHandlingMode::Strict);
- defaultParameters.setSettingsDirectory(m_settings->baseDirectory());
}
-TestLanguage::~TestLanguage()
-{
-}
+TestLanguage::~TestLanguage() = default;
QHash<QString, ResolvedProductPtr> TestLanguage::productsFromProject(ResolvedProjectPtr project)
{
@@ -143,8 +139,7 @@ void TestLanguage::handleInitCleanupDataTags(const char *projectFileName, bool *
*handled = true;
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject(projectFileName));
- project = loader->loadProject(defaultParameters);
+ resolveProject(projectFileName);
QVERIFY(!!project);
} catch (const ErrorInfo &e) {
exceptionCaught = true;
@@ -159,9 +154,27 @@ void TestLanguage::handleInitCleanupDataTags(const char *projectFileName, bool *
}
}
+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);
+}
+
void TestLanguage::init()
{
m_logSink->setLogLevel(LoggerInfo);
+ defaultParameters = {};
+ defaultParameters.setBuildRoot(m_tempDir.path() + "/buildroot");
+ defaultParameters.setPropertyCheckingMode(ErrorHandlingMode::Strict);
+ defaultParameters.setSettingsDirectory(m_settings->baseDirectory());
+ defaultParameters.setTopLevelProfile(profileName());
+ defaultParameters.setConfigurationName("default");
+ defaultParameters.setEnvironment(QProcessEnvironment::systemEnvironment());
+ defaultParameters.setSearchPaths({SRCDIR "/../../../share/qbs"});
+
QVERIFY(m_tempDir.isValid());
}
@@ -177,27 +190,15 @@ void TestLanguage::initTestCase()
{
m_logger = Logger(m_logSink);
m_engine = ScriptEngine::create(m_logger, EvalContext::PropertyEvaluation);
- loader = new Loader(m_engine.get(), m_logger);
- loader->setSearchPaths(QStringList()
- << (testDataDir() + "/../../../../share/qbs"));
- defaultParameters.setTopLevelProfile(profileName());
- defaultParameters.setConfigurationName("default");
- defaultParameters.expandBuildConfiguration();
- defaultParameters.setEnvironment(QProcessEnvironment::systemEnvironment());
- QVERIFY(QFileInfo(m_wildcardsTestDirPath).isAbsolute());
-}
-void TestLanguage::cleanupTestCase()
-{
- delete loader;
+ QVERIFY(QFileInfo(m_wildcardsTestDirPath).isAbsolute());
}
void TestLanguage::additionalProductTypes()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("additional-product-types.qbs"));
- project = loader->loadProject(defaultParameters);
+ resolveProject("additional-product-types.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
const ResolvedProductConstPtr product = products.value("p");
@@ -218,8 +219,7 @@ void TestLanguage::baseProperty()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("baseproperty.qbs"));
- project = loader->loadProject(defaultParameters);
+ resolveProject("baseproperty.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
ResolvedProductPtr product = products.value("product1");
@@ -236,10 +236,8 @@ void TestLanguage::baseProperty()
void TestLanguage::baseValidation()
{
- qbs::SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("base-validate/base-validate.qbs"));
try {
- project = loader->loadProject(params);
+ resolveProject("base-validate/base-validate.qbs");
QVERIFY2(false, "exception expected");
} catch (const qbs::ErrorInfo &e) {
QVERIFY2(e.toString().contains("Parent succeeded, child failed."),
@@ -249,11 +247,9 @@ void TestLanguage::baseValidation()
void TestLanguage::brokenDependencyCycle()
{
- qbs::SetupProjectParameters params = defaultParameters;
QFETCH(QString, projectFileName);
- params.setProjectFilePath(testProject(qPrintable(projectFileName)));
try {
- project = loader->loadProject(params);
+ resolveProject(qPrintable(projectFileName));
} catch (const qbs::ErrorInfo &e) {
QVERIFY2(false, qPrintable(e.toString()));
}
@@ -270,12 +266,10 @@ void TestLanguage::buildConfigStringListSyntax()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters parameters = defaultParameters;
QVariantMap overriddenValues;
overriddenValues.insert("project.someStrings", "foo,bar,baz");
- parameters.setOverriddenValues(overriddenValues);
- parameters.setProjectFilePath(testProject("buildconfigstringlistsyntax.qbs"));
- project = loader->loadProject(parameters);
+ defaultParameters.setOverriddenValues(overriddenValues);
+ resolveProject("buildconfigstringlistsyntax.qbs");
QVERIFY(!!project);
QCOMPARE(project->projectProperties().value("someStrings").toStringList(),
QStringList() << "foo" << "bar" << "baz");
@@ -290,9 +284,7 @@ void TestLanguage::builtinFunctionInSearchPathsProperty()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters parameters = defaultParameters;
- parameters.setProjectFilePath(testProject("builtinFunctionInSearchPathsProperty.qbs"));
- QVERIFY(!!loader->loadProject(parameters));
+ QVERIFY(resolveProject("builtinFunctionInSearchPathsProperty.qbs"));
} catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
@@ -304,9 +296,7 @@ void TestLanguage::chainedProbes()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters parameters = defaultParameters;
- parameters.setProjectFilePath(testProject("chained-probes/chained-probes.qbs"));
- const TopLevelProjectConstPtr project = loader->loadProject(parameters);
+ resolveProject("chained-probes/chained-probes.qbs");
QVERIFY(!!project);
QCOMPARE(project->products.size(), size_t(1));
const QString prop1Val = project->products.front()->moduleProperties
@@ -327,9 +317,7 @@ void TestLanguage::versionCompare()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters parameters = defaultParameters;
- parameters.setProjectFilePath(testProject("versionCompare.qbs"));
- QVERIFY(!!loader->loadProject(parameters));
+ QVERIFY(resolveProject("versionCompare.qbs"));
} catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
@@ -341,8 +329,7 @@ void TestLanguage::canonicalArchitecture()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("canonicalArchitecture.qbs"));
- project = loader->loadProject(defaultParameters);
+ resolveProject("canonicalArchitecture.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
ResolvedProductPtr product = products.value(QStringLiteral("x86"));
@@ -358,8 +345,7 @@ void TestLanguage::rfc1034Identifier()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("rfc1034identifier.qbs"));
- project = loader->loadProject(defaultParameters);
+ resolveProject("rfc1034identifier.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
ResolvedProductPtr product = products.value(QStringLiteral("this-has-special-characters-"
@@ -395,10 +381,8 @@ void TestLanguage::throwThings()
QFETCH(QString, result);
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("throw.qbs"));
- params.setOverriddenValues({{"project.throwType", type}});
- loader->loadProject(params);
+ defaultParameters.setOverriddenValues({{"project.throwType", type}});
+ resolveProject("throw.qbs");
} catch (const ErrorInfo &e) {
exceptionCaught = true;
QVERIFY2(e.toString().contains(result), qPrintable(e.toString()));
@@ -412,11 +396,9 @@ void TestLanguage::conditionalDepends()
ResolvedProductPtr product;
ResolvedModuleConstPtr dependency;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("conditionaldepends.qbs"));
- params.setOverriddenValues({std::make_pair(QString("products."
+ defaultParameters.setOverriddenValues({std::make_pair(QString("products."
"multilevel_module_props_overridden.dummy3.loadDummy"), true)});
- project = loader->loadProject(params);
+ resolveProject("conditionaldepends.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
@@ -520,12 +502,10 @@ void TestLanguage::delayedError()
QFETCH(bool, productEnabled);
try {
QFETCH(QString, projectFileName);
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject(projectFileName.toLatin1()));
QVariantMap overriddenValues;
overriddenValues.insert("project.enableProduct", productEnabled);
- params.setOverriddenValues(overriddenValues);
- project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(overriddenValues);
+ resolveProject(projectFileName.toLatin1());
QCOMPARE(productEnabled, false);
QVERIFY(!!project);
QCOMPARE(project->products.size(), size_t(1));
@@ -557,8 +537,6 @@ void TestLanguage::dependencyOnAllProfiles()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("dependencyOnAllProfiles.qbs"));
TemporaryProfile p1("p1", m_settings);
p1.p.setValue("qbs.architecture", "arch1");
TemporaryProfile p2("p2", m_settings);
@@ -566,8 +544,8 @@ void TestLanguage::dependencyOnAllProfiles()
QVariantMap overriddenValues;
overriddenValues.insert("project.profile1", "p1");
overriddenValues.insert("project.profile2", "p2");
- params.setOverriddenValues(overriddenValues);
- project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(overriddenValues);
+ resolveProject("dependencyOnAllProfiles.qbs");
QVERIFY(!!project);
QCOMPARE(project->products.size(), size_t(3));
const ResolvedProductConstPtr mainProduct = productsFromProject(project).value("main");
@@ -588,9 +566,7 @@ void TestLanguage::derivedSubProject()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("derived-sub-project/project.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(params);
+ resolveProject("derived-sub-project/project.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -605,9 +581,7 @@ void TestLanguage::disabledSubProject()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("disabled-subproject.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(params);
+ resolveProject("disabled-subproject.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 0);
@@ -636,16 +610,14 @@ void TestLanguage::dottedNames()
{
QFETCH(bool, expectSuccess);
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("dotted-names/dotted-names.qbs"));
QFETCH(bool, useProduct);
QFETCH(bool, useModule);
const QVariantMap overridden{
std::make_pair("projects.theProject.includeDottedProduct", useProduct),
std::make_pair("projects.theProject.includeDottedModule", useModule)
};
- params.setOverriddenValues(overridden);
- TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(overridden);
+ resolveProject("dotted-names/dotted-names.qbs");
QVERIFY(expectSuccess);
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
@@ -662,13 +634,44 @@ void TestLanguage::dottedNames()
}
}
+void TestLanguage::duplicateMultiplexValues_data()
+{
+ QTest::addColumn<bool>("dummy");
+ QTest::newRow("duplicate-multiplex-value") << true;
+ QTest::newRow("duplicate-multiplex-value2") << true;
+}
+
+void TestLanguage::duplicateMultiplexValues()
+{
+ bool exceptionCaught = false;
+ try {
+ resolveProject(qPrintable(QString::fromLocal8Bit(QTest::currentDataTag())
+ + QLatin1String(".qbs")));
+ QVERIFY(project);
+ const std::vector<ResolvedProductPtr> products = project->allProducts();
+ QCOMPARE(products.size(), 2);
+ bool x86 = false;
+ bool arm = false;
+ for (const ResolvedProductPtr &p : products) {
+ if (p->moduleProperties->moduleProperty("qbs", "architecture").toString() == "x86")
+ x86 = true;
+ else if (p->moduleProperties->moduleProperty("qbs", "architecture").toString() == "arm")
+ arm = true;
+ }
+ QVERIFY(x86);
+ QVERIFY(arm);
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
+ qDebug() << e.toString();
+ }
+ QVERIFY(!exceptionCaught);
+}
+
void TestLanguage::emptyJsFile()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("empty-js-file.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(params);
+ resolveProject("empty-js-file.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -683,9 +686,7 @@ void TestLanguage::enumerateProjectProperties()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("enum-project-props.qbs"));
- auto project = loader->loadProject(params);
+ resolveProject("enum-project-props.qbs");
QVERIFY(!!project);
auto products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -708,7 +709,7 @@ void TestLanguage::evalErrorInNonPresentModule_data()
QTest::addColumn<QString>("errorMessage");
QTest::newRow("module required")
- << true << "broken.qbs:4:5 Element at index 0 of list property 'broken' "
+ << true << "broken.qbs:2:5 Element at index 0 of list property 'broken' "
"does not have string type";
QTest::newRow("module not required") << false << QString();
}
@@ -718,19 +719,16 @@ void TestLanguage::evalErrorInNonPresentModule()
QFETCH(bool, moduleRequired);
QFETCH(QString, errorMessage);
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("eval-error-in-non-present-module.qbs"));
QVariantMap overridden{std::make_pair("products.p.moduleRequired", moduleRequired)};
- params.setOverriddenValues(overridden);
- TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(overridden);
+ resolveProject("eval-error-in-non-present-module.qbs");
QVERIFY(errorMessage.isEmpty());
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
const ResolvedProductPtr product = products.value("p");
QVERIFY(!!product);
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
QVERIFY(!errorMessage.isEmpty());
QVERIFY2(e.toString().contains(errorMessage), qPrintable(e.toString()));
}
@@ -740,14 +738,12 @@ void TestLanguage::defaultValue()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("defaultvalue/egon.qbs"));
QFETCH(QString, prop1Value);
QVariantMap overridden;
if (!prop1Value.isEmpty())
overridden.insert("modules.lower.prop1", prop1Value);
- params.setOverriddenValues(overridden);
- TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(overridden);
+ resolveProject("defaultvalue/egon.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 2);
@@ -761,8 +757,7 @@ void TestLanguage::defaultValue()
propertyValue = product->moduleProperties->property(propertyName);
QFETCH(QVariant, expectedListPropValue);
QCOMPARE(propertyValue.toStringList(), expectedListPropValue.toStringList());
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -797,10 +792,7 @@ void TestLanguage::environmentVariable()
QProcessEnvironment origEnv = defaultParameters.environment(); // store orig environment
defaultParameters.setEnvironment(env);
- defaultParameters.setProjectFilePath(testProject("environmentvariable.qbs"));
- project = loader->loadProject(defaultParameters);
-
- defaultParameters.setEnvironment(origEnv); // reset environment
+ resolveProject("environmentvariable.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
@@ -817,9 +809,7 @@ void TestLanguage::errorInDisabledProduct()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("error-in-disabled-product.qbs"));
- auto project = loader->loadProject(params);
+ resolveProject("error-in-disabled-product.qbs");
QVERIFY(!!project);
auto products = productsFromProject(project);
QCOMPARE(products.size(), 5);
@@ -934,8 +924,6 @@ void TestLanguage::erroneousFiles_data()
QTest::newRow("conflicting-module-instances")
<< "There is more than one equally prioritized candidate for module "
"'conflicting-instances'.";
- QTest::newRow("module-depends-on-product")
- << "module-with-product-dependency.qbs:2:5.*Modules cannot depend on products.";
QTest::newRow("overwrite-inherited-readonly-property")
<< "overwrite-inherited-readonly-property.qbs"
":2:21.*Cannot set read-only property 'readOnlyString'.";
@@ -975,11 +963,6 @@ void TestLanguage::erroneousFiles_data()
QTest::newRow("dependency-profile-mismatch-2")
<< "dependency-profile-mismatch-2.qbs:15:9 Dependency from product 'main' to "
"product 'dep' not fulfilled. There are no eligible multiplex candidates.";
- QTest::newRow("duplicate-multiplex-value")
- << "duplicate-multiplex-value.qbs:1:1.*Duplicate entry 'x86' in qbs.architectures.";
- QTest::newRow("duplicate-multiplex-value2")
- << "duplicate-multiplex-value2.qbs:1:1.*Duplicate entry 'architecture' in "
- "Product.multiplexByQbsProperties.";
QTest::newRow("invalid-references")
<< "invalid-references.qbs:2:17.*Cannot open '.*nosuchproject.qbs'";
QTest::newRow("missing-js-file")
@@ -991,8 +974,7 @@ void TestLanguage::erroneousFiles()
QFETCH(QString, errorMessage);
QString fileName = QString::fromLocal8Bit(QTest::currentDataTag()) + QLatin1String(".qbs");
try {
- defaultParameters.setProjectFilePath(testProject("/erroneous/") + fileName);
- loader->loadProject(defaultParameters);
+ resolveProject(qPrintable("/erroneous/" + fileName));
} catch (const ErrorInfo &e) {
const QRegularExpression reg(errorMessage, QRegularExpression::DotMatchesEverythingOption);
if (!e.toString().contains(reg)) {
@@ -1011,8 +993,7 @@ void TestLanguage::exports()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("exports.qbs"));
- TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("exports.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 22);
@@ -1125,8 +1106,7 @@ void TestLanguage::exports()
|| m->name == QString("broken_cycle2"),
qPrintable(m->name));
}
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -1137,8 +1117,7 @@ void TestLanguage::fileContextProperties()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("filecontextproperties.qbs"));
- project = loader->loadProject(defaultParameters);
+ resolveProject("filecontextproperties.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
ResolvedProductPtr product = products.value("product1");
@@ -1184,14 +1163,12 @@ void TestLanguage::fileInProductAndModule()
QFETCH(bool, addFileToProduct);
QFETCH(bool, successExpected);
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("file-in-product-and-module.qbs"));
- params.setOverriddenValues(QVariantMap{
+ defaultParameters.setOverriddenValues(QVariantMap{
std::make_pair("modules.module_with_file.file1IsTarget", file1IsTarget),
std::make_pair("modules.module_with_file.file2IsTarget", file2IsTarget),
std::make_pair("products.p.addFileToProduct", addFileToProduct),
});
- project = loader->loadProject(params);
+ resolveProject("file-in-product-and-module.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -1206,8 +1183,7 @@ void TestLanguage::getNativeSetting()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("getNativeSetting.qbs"));
- project = loader->loadProject(defaultParameters);
+ resolveProject("getNativeSetting.qbs");
QString expectedTargetName;
if (HostOsInfo::isMacosHost()) {
@@ -1282,8 +1258,7 @@ void TestLanguage::groupName()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("groupname.qbs"));
- TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("groupname.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 2);
@@ -1310,8 +1285,7 @@ void TestLanguage::groupName()
group = product->groups.at(2);
QVERIFY(!!group);
QCOMPARE(group->name, QString("Group 2"));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -1321,8 +1295,7 @@ void TestLanguage::groupName()
void TestLanguage::homeDirectory()
{
try {
- defaultParameters.setProjectFilePath(testProject("homeDirectory.qbs"));
- ResolvedProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("homeDirectory.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -1347,8 +1320,7 @@ void TestLanguage::homeDirectory()
FileInfo::resolvePath(product->sourceDirectory, QStringLiteral("a/~/bb")));
QCOMPARE(product->productProperties.value("user").toString(),
FileInfo::resolvePath(product->sourceDirectory, QStringLiteral("~foo/bar")));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
qDebug() << e.toString();
}
}
@@ -1427,8 +1399,7 @@ void TestLanguage::idUsage()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("idusage.qbs"));
- TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("idusage.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 5);
@@ -1452,8 +1423,7 @@ void TestLanguage::idUsage()
QVERIFY(!!product5);
QCOMPARE(product5->moduleProperties->moduleProperty("deepdummy.deep.moat", "zort")
.toString(), QString("zort in dummy"));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -1464,10 +1434,8 @@ void TestLanguage::idUniqueness()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("id-uniqueness.qbs"));
- loader->loadProject(defaultParameters);
- }
- catch (const ErrorInfo &e) {
+ resolveProject("id-uniqueness.qbs");
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
const QList<ErrorItem> items = e.items();
QCOMPARE(items.size(), 3);
@@ -1482,15 +1450,13 @@ void TestLanguage::importCollection()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("import-collection/project.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("import-collection/project.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
const ResolvedProductConstPtr product = products.value("da product");
QCOMPARE(product->productProperties.value("targetName").toString(),
QLatin1String("C1f1C1f2C2f1C2f2"));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -1509,19 +1475,15 @@ void TestLanguage::inheritedPropertiesItems()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
QFETCH(QString, buildVariant);
QFETCH(QString, productName);
- params.setProjectFilePath
- (testProject("inherited-properties-items/inherited-properties-items.qbs"));
- params.setOverriddenValues(QVariantMap{std::make_pair("qbs.buildVariant", buildVariant)});
- TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(QVariantMap{std::make_pair("qbs.buildVariant", buildVariant)});
+ resolveProject("inherited-properties-items/inherited-properties-items.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
QVERIFY(!!products.value(productName));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -1532,13 +1494,11 @@ void TestLanguage::invalidBindingInDisabledItem()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("invalidBindingInDisabledItem.qbs"));
- TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("invalidBindingInDisabledItem.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 2);
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -1552,13 +1512,10 @@ void TestLanguage::invalidOverrides()
const bool successExpected = expectedErrorMessage.isEmpty();
bool exceptionCaught = false;
try {
- qbs::SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("invalid-overrides.qbs"));
- params.setOverriddenValues(QVariantMap{std::make_pair(key, true)});
- const TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(QVariantMap{std::make_pair(key, true)});
+ resolveProject("invalid-overrides.qbs");
QVERIFY(!!project);
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
if (successExpected)
qDebug() << e.toString();
@@ -1605,25 +1562,20 @@ void TestLanguage::invalidOverrides_data()
class JSSourceValueCreator
{
FileContextPtr m_fileContext;
- QList<QString *> m_strings;
+ std::vector<std::unique_ptr<QString>> m_strings;
public:
JSSourceValueCreator(const FileContextPtr &fileContext)
: m_fileContext(fileContext)
{
}
- ~JSSourceValueCreator()
- {
- qDeleteAll(m_strings);
- }
-
JSSourceValuePtr create(const QString &sourceCode)
{
JSSourceValuePtr value = JSSourceValue::create();
value->setFile(m_fileContext);
- const auto str = new QString(sourceCode);
- m_strings.push_back(str);
- value->setSourceCode(QStringRef(str));
+ auto str = std::make_unique<QString>(sourceCode);
+ value->setSourceCode(*str.get());
+ m_strings.push_back(std::move(str));
return value;
}
};
@@ -1704,20 +1656,16 @@ void TestLanguage::jsImportUsedInMultipleScopes()
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("jsimportsinmultiplescopes.qbs"));
- params.setOverriddenValues({std::make_pair(QStringLiteral("qbs.buildVariant"),
- buildVariant)});
- params.expandBuildConfiguration();
- TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues({std::make_pair(QStringLiteral("qbs.buildVariant"),
+ buildVariant)});
+ resolveProject("jsimportsinmultiplescopes.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
ResolvedProductPtr product = products.values().front();
QVERIFY(!!product);
QCOMPARE(product->name, expectedProductName);
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -1728,11 +1676,7 @@ void TestLanguage::moduleMergingVariantValues()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath
- (testProject("module-merging-variant-values/module-merging-variant-values.qbs"));
- params.expandBuildConfiguration();
- const TopLevelProjectPtr project = loader->loadProject(params);
+ resolveProject("module-merging-variant-values/module-merging-variant-values.qbs");
QVERIFY(!!project);
QCOMPARE(int(project->products.size()), 2);
} catch (const ErrorInfo &e) {
@@ -1757,12 +1701,10 @@ void TestLanguage::modulePrioritizationBySearchPath()
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("module-prioritization-by-search-path/project.qbs"));
- params.setOverriddenValues({std::make_pair(QStringLiteral("project.qbsSearchPaths"),
- searchPaths)});
- params.expandBuildConfiguration();
- TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(
+ {std::make_pair(QStringLiteral("project.qbsSearchPaths"),
+ searchPaths)});
+ resolveProject("module-prioritization-by-search-path/project.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -1771,8 +1713,7 @@ void TestLanguage::modulePrioritizationBySearchPath()
const QString actualVariant = product->moduleProperties->moduleProperty
("conflicting-instances", "moduleVariant").toString();
QCOMPARE(actualVariant, expectedVariant);
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -1838,7 +1779,7 @@ void TestLanguage::modulePropertiesInGroups()
defaultParameters.setProjectFilePath(testProject("modulepropertiesingroups.qbs"));
bool exceptionCaught = false;
try {
- TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject();
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
ResolvedProductPtr product = products.value("grouptest");
@@ -1966,7 +1907,7 @@ void TestLanguage::modulePropertiesInGroups()
QCOMPARE(g2Gmod1List1, QStringList() << "gmod1_list1_proto" << "g2");
const auto &g2Gmod1List2 = moduleProperty(g2Props, "gmod.gmod1", "gmod1_list2")
.toStringList();
- QCOMPARE(g2Gmod1List2, QStringList() << "grouptest" << "g2" << "gmod1_list2_proto");
+ QCOMPARE(g2Gmod1List2, QStringList() << "grouptest" << "gmod1_string_proto" << "gmod1_list2_proto");
const int g2P0 = moduleProperty(g2Props, "gmod.gmod1", "p0").toInt();
QCOMPARE(g2P0, 6);
const int g2DepProp = moduleProperty(g2Props, "gmod.gmod1", "depProp").toInt();
@@ -2050,17 +1991,14 @@ void TestLanguage::modulePropertyOverridesPerProduct()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setOverriddenValues({
+ defaultParameters.setOverriddenValues({
std::make_pair("modules.dummy.rpaths", QStringList({"/usr/lib"})),
std::make_pair("modules.dummy.someString", "m"),
std::make_pair("products.b.dummy.someString", "b"),
std::make_pair("products.c.dummy.someString", "c"),
std::make_pair("products.c.dummy.rpaths", QStringList({"/home", "/tmp"}))
});
- params.setProjectFilePath(
- testProject("module-property-overrides-per-product.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(params);
+ resolveProject("module-property-overrides-per-product.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 3);
@@ -2093,8 +2031,7 @@ void TestLanguage::modulePropertyOverridesPerProduct()
QCOMPARE(listPropertyValue(a), productPropertyValue(a));
QCOMPARE(listPropertyValue(b), productPropertyValue(b));
QCOMPARE(listPropertyValue(c), productPropertyValue(c));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -2106,7 +2043,7 @@ void TestLanguage::moduleScope()
bool exceptionCaught = false;
try {
defaultParameters.setProjectFilePath(testProject("modulescope.qbs"));
- TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject();
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -2126,13 +2063,30 @@ void TestLanguage::moduleScope()
QCOMPARE(intModuleValue("f"), 2); // overridden
QCOMPARE(intModuleValue("g"), 156); // overridden, dependent on product properties
QCOMPARE(intModuleValue("h"), 158); // overridden, base dependent on product properties
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
QCOMPARE(exceptionCaught, false);
+}
+void TestLanguage::moduleWithProductDependency()
+{
+ bool exceptionCaught = false;
+ try {
+ defaultParameters.setProjectFilePath(testProject("module-depends-on-product.qbs"));
+ resolveProject();
+ QVERIFY(project);
+ QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
+ QCOMPARE(products.size(), 2);
+ ResolvedProductPtr product = products.value("p1");
+ QVERIFY(product);
+ QCOMPARE(int(product->dependencies.size()), 1);
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
+ qDebug() << e.toString();
+ }
+ QCOMPARE(exceptionCaught, false);
}
void TestLanguage::modules_data()
@@ -2191,9 +2145,7 @@ void TestLanguage::multiplexedExports()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("multiplexed-exports.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(params);
+ resolveProject("multiplexed-exports.qbs");
QVERIFY(!!project);
const auto products = project->allProducts();
QCOMPARE(products.size(), size_t(4));
@@ -2227,11 +2179,9 @@ void TestLanguage::multiplexingByProfile()
{
QFETCH(QString, projectFileName);
QFETCH(bool, successExpected);
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testDataDir() + "/multiplexing-by-profile/" + projectFileName);
try {
- params.setDryRun(true);
- const TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setDryRun(true);
+ resolveProject(qPrintable("/multiplexing-by-profile/" + projectFileName));
QVERIFY(successExpected);
QVERIFY(!!project);
} catch (const ErrorInfo &e) {
@@ -2255,11 +2205,9 @@ void TestLanguage::nonApplicableModulePropertyInProfile()
QFETCH(QString, toolchain);
QFETCH(bool, successExpected);
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("non-applicable-module-property-in-profile.qbs"));
- params.setOverriddenValues(QVariantMap{std::make_pair("project.targetOS", targetOS),
+ defaultParameters.setOverriddenValues({std::make_pair("project.targetOS", targetOS),
std::make_pair("project.toolchain", toolchain)});
- const TopLevelProjectPtr project = loader->loadProject(params);
+ resolveProject("non-applicable-module-property-in-profile.qbs");
QVERIFY(!!project);
QVERIFY(successExpected);
} catch (const ErrorInfo &e) {
@@ -2289,8 +2237,6 @@ void TestLanguage::nonRequiredProducts()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("non-required-products.qbs"));
QFETCH(bool, subProjectEnabled);
QFETCH(bool, dependeeEnabled);
QVariantMap overriddenValues;
@@ -2298,8 +2244,8 @@ void TestLanguage::nonRequiredProducts()
overriddenValues.insert("projects.subproject.condition", false);
else if (!dependeeEnabled)
overriddenValues.insert("products.dependee.condition", false);
- params.setOverriddenValues(overriddenValues);
- const TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(overriddenValues);
+ resolveProject("non-required-products.qbs");
QVERIFY(!!project);
const auto products = productsFromProject(project);
QCOMPARE(products.size(), 4 + !!subProjectEnabled);
@@ -2318,8 +2264,7 @@ void TestLanguage::nonRequiredProducts()
QVERIFY2(product, name);
QVERIFY2(!product->enabled, name);
}
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -2340,7 +2285,7 @@ void TestLanguage::outerInGroup()
bool exceptionCaught = false;
try {
defaultParameters.setProjectFilePath(testProject("outerInGroup.qbs"));
- TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject();
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -2361,8 +2306,7 @@ void TestLanguage::outerInGroup()
artifact = group->files.front();
installDir = artifact->properties->qbsPropertyValue("installDir");
QCOMPARE(installDir.toString(), QString("/somewhere/else"));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -2375,16 +2319,14 @@ void TestLanguage::overriddenPropertiesAndPrototypes()
try {
QFETCH(QString, osName);
QFETCH(QString, backendName);
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("overridden-properties-and-prototypes.qbs"));
- params.setOverriddenValues({std::make_pair("modules.qbs.targetPlatform", osName)});
- TopLevelProjectConstPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues({std::make_pair("modules.qbs.targetPlatform",
+ osName)});
+ resolveProject("overridden-properties-and-prototypes.qbs");
QVERIFY(!!project);
QCOMPARE(project->products.size(), size_t(1));
QCOMPARE(project->products.front()->moduleProperties->moduleProperty(
"multiple_backends", "prop").toString(), backendName);
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -2403,11 +2345,9 @@ void TestLanguage::overriddenVariantProperty()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
const QVariantMap objectValue{std::make_pair("x", 1), std::make_pair("y", 2)};
- params.setOverriddenValues({std::make_pair("products.p.myObject", objectValue)});
- params.setProjectFilePath(testProject("overridden-variant-property.qbs"));
- TopLevelProjectConstPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues({std::make_pair("products.p.myObject", objectValue)});
+ resolveProject("overridden-variant-property.qbs");
QVERIFY(!!project);
QCOMPARE(project->products.size(), size_t(1));
QCOMPARE(project->products.front()->productProperties.value("myObject").toMap(),
@@ -2424,9 +2364,8 @@ void TestLanguage::parameterTypes()
bool exceptionCaught = false;
try {
defaultParameters.setProjectFilePath(testProject("parameter-types.qbs"));
- loader->loadProject(defaultParameters);
- }
- catch (const ErrorInfo &e) {
+ resolveProject();
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -2437,8 +2376,7 @@ void TestLanguage::pathProperties()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("pathproperties.qbs"));
- project = loader->loadProject(defaultParameters);
+ resolveProject("pathproperties.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
ResolvedProductPtr product = products.value("product1");
@@ -2474,14 +2412,11 @@ void TestLanguage::profileValuesAndOverriddenValues()
profile.setValue("dummy.cFlags", "IN_PROFILE");
profile.setValue("dummy.cxxFlags", "IN_PROFILE");
profile.setValue("qbs.architecture", "x86");
- SetupProjectParameters parameters = defaultParameters;
- parameters.setTopLevelProfile(profile.name());
+ defaultParameters.setTopLevelProfile(profile.name());
QVariantMap overriddenValues;
overriddenValues.insert("modules.dummy.cFlags", "OVERRIDDEN");
- parameters.setOverriddenValues(overriddenValues);
- parameters.setProjectFilePath(testProject("profilevaluesandoverriddenvalues.qbs"));
- parameters.expandBuildConfiguration();
- project = loader->loadProject(parameters);
+ defaultParameters.setOverriddenValues(overriddenValues);
+ resolveProject("profilevaluesandoverriddenvalues.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
ResolvedProductPtr product = products.value("product1");
@@ -2511,7 +2446,7 @@ void TestLanguage::projectFileLookup()
try {
SetupProjectParameters params;
params.setProjectFilePath(projectFileInput);
- Loader::setupProjectFilePath(params);
+ params.finalizeProjectFilePath();
QVERIFY(!failureExpected);
QCOMPARE(params.projectFilePath(), projectFileOutput);
} catch (const ErrorInfo &) {
@@ -2542,8 +2477,7 @@ void TestLanguage::productConditions()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("productconditions.qbs"));
- TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("productconditions.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 6);
@@ -2571,8 +2505,7 @@ void TestLanguage::productConditions()
product = products.value("product_probe_condition_true");
QVERIFY(!!product);
QVERIFY(product->enabled);
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -2583,8 +2516,7 @@ void TestLanguage::productDirectories()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("productdirectories.qbs"));
- ResolvedProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("productdirectories.qbs");
QVERIFY(!!project);
QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 1);
@@ -2595,8 +2527,7 @@ void TestLanguage::productDirectories()
QCOMPARE(config.value(QStringLiteral("buildDirectory")).toString(),
product->buildDirectory());
QCOMPARE(config.value(QStringLiteral("sourceDirectory")).toString(), testDataDir());
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
exceptionCaught = true;
qDebug() << e.toString();
}
@@ -2742,8 +2673,7 @@ void TestLanguage::propertiesBlockInGroup()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("properties-block-in-group.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("properties-block-in-group.qbs");
QVERIFY(!!project);
QCOMPARE(project->allProducts().size(), size_t(1));
const ResolvedProductConstPtr product = project->allProducts().front();
@@ -2767,9 +2697,7 @@ void TestLanguage::propertiesItemInModule()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(
- testProject("properties-item-in-module.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("properties-item-in-module.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 2);
@@ -2788,9 +2716,7 @@ void TestLanguage::propertyAssignmentInExportedGroup()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(
- testProject("property-assignment-in-exported-group.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("property-assignment-in-exported-group.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 2);
@@ -2817,8 +2743,7 @@ void TestLanguage::qbs1275()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(testProject("qbs1275.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("qbs1275.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.count(), 5);
@@ -2833,9 +2758,7 @@ void TestLanguage::qbsPropertiesInProjectCondition()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(
- testProject("qbs-properties-in-project-condition.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("qbs-properties-in-project-condition.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 0);
@@ -2850,16 +2773,14 @@ void TestLanguage::qbsPropertyConvenienceOverride()
{
bool exceptionCaught = false;
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("qbs-property-convenience-override.qbs"));
- params.setOverriddenValues({std::make_pair("qbs.installPrefix", "/opt")});
- TopLevelProjectConstPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues({std::make_pair("qbs.installPrefix", "/opt")});
+ resolveProject("qbs-property-convenience-override.qbs");
QVERIFY(!!project);
QCOMPARE(project->products.size(), size_t(1));
QCOMPARE(project->products.front()->moduleProperties->qbsPropertyValue("installPrefix")
.toString(), QString("/opt"));
- }
- catch (const ErrorInfo &e) {
+ } catch (const ErrorInfo &e) {
+ exceptionCaught = true;
qDebug() << e.toString();
}
QCOMPARE(exceptionCaught, false);
@@ -2870,11 +2791,9 @@ void TestLanguage::relaxedErrorMode()
m_logSink->setLogLevel(LoggerMinLevel);
QFETCH(bool, strictMode);
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("relaxed-error-mode/relaxed-error-mode.qbs"));
- params.setProductErrorMode(strictMode ? ErrorHandlingMode::Strict
- : ErrorHandlingMode::Relaxed);
- const TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setProductErrorMode(strictMode ? ErrorHandlingMode::Strict
+ : ErrorHandlingMode::Relaxed);
+ resolveProject("relaxed-error-mode/relaxed-error-mode.qbs");
QVERIFY(!strictMode);
const auto productMap = productsFromProject(project);
const ResolvedProductConstPtr brokenProduct = productMap.value("broken");
@@ -2919,10 +2838,7 @@ void TestLanguage::requiredAndNonRequiredDependencies()
QFETCH(QString, projectFile);
QFETCH(bool, exceptionExpected);
try {
- SetupProjectParameters params = defaultParameters;
- const QString projectFilePath = "required-and-nonrequired-dependencies/" + projectFile;
- params.setProjectFilePath(testProject(projectFilePath.toLocal8Bit()));
- const TopLevelProjectConstPtr project = loader->loadProject(params);
+ resolveProject(qPrintable("required-and-nonrequired-dependencies/" + projectFile));
QVERIFY(!!project);
QVERIFY(!exceptionExpected);
} catch (const ErrorInfo &e) {
@@ -2949,10 +2865,7 @@ void TestLanguage::requiredAndNonRequiredDependencies_data()
void TestLanguage::suppressedAndNonSuppressedErrors()
{
try {
- SetupProjectParameters params = defaultParameters;
- const QString projectFilePath = "suppressed-and-non-suppressed-errors.qbs";
- params.setProjectFilePath(testProject(projectFilePath.toLocal8Bit()));
- const TopLevelProjectConstPtr project = loader->loadProject(params);
+ resolveProject("suppressed-and-non-suppressed-errors.qbs");
QFAIL("failure expected");
} catch (const ErrorInfo &e) {
QVERIFY2(e.toString().contains("easter bunny"), qPrintable(e.toString()));
@@ -2964,12 +2877,10 @@ void TestLanguage::throwingProbe()
{
QFETCH(bool, enableProbe);
try {
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("throwing-probe.qbs"));
QVariantMap properties;
properties.insert(QStringLiteral("products.theProduct.enableProbe"), enableProbe);
- params.setOverriddenValues(properties);
- const TopLevelProjectPtr project = loader->loadProject(params);
+ defaultParameters.setOverriddenValues(properties);
+ resolveProject("throwing-probe.qbs");
QVERIFY(!!project);
QVERIFY(!enableProbe);
} catch (const ErrorInfo &e) {
@@ -3011,9 +2922,7 @@ void TestLanguage::recursiveProductDependencies()
{
bool exceptionCaught = false;
try {
- defaultParameters.setProjectFilePath(
- testProject("recursive-dependencies/recursive-dependencies.qbs"));
- const TopLevelProjectPtr project = loader->loadProject(defaultParameters);
+ resolveProject("recursive-dependencies/recursive-dependencies.qbs");
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
QCOMPARE(products.size(), 4);
@@ -3073,10 +2982,8 @@ void TestLanguage::fileTags()
void TestLanguage::useInternalProfile()
{
const QString profile(QStringLiteral("theprofile"));
- SetupProjectParameters params = defaultParameters;
- params.setProjectFilePath(testProject("use-internal-profile.qbs"));
- params.setTopLevelProfile(profile);
- TopLevelProjectConstPtr project = loader->loadProject(params);
+ defaultParameters.setTopLevelProfile(profile);
+ resolveProject("use-internal-profile.qbs");
QVERIFY(!!project);
QCOMPARE(project->profile(), profile);
QCOMPARE(project->products.size(), size_t(1));
@@ -3272,7 +3179,7 @@ void TestLanguage::wildcards()
}
// create files
- for (QString filePath : qAsConst(filesToCreate)) {
+ for (QString filePath : std::as_const(filesToCreate)) {
filePath.prepend(m_wildcardsTestDirPath + '/');
QFileInfo fi(filePath);
if (!QDir(fi.path()).exists())
@@ -3286,7 +3193,7 @@ void TestLanguage::wildcards()
ResolvedProductPtr product;
try {
defaultParameters.setProjectFilePath(projectFilePath);
- project = loader->loadProject(defaultParameters);
+ resolveProject();
QVERIFY(!!project);
const QHash<QString, ResolvedProductPtr> products = productsFromProject(project);
product = products.value("MyProduct");
diff --git a/tests/auto/language/tst_language.h b/tests/auto/language/tst_language.h
index e72259a0f..3820e3f7c 100644
--- a/tests/auto/language/tst_language.h
+++ b/tests/auto/language/tst_language.h
@@ -41,8 +41,9 @@
#define TST_LANGUAGE_H
#include <language/forward_decls.h>
-#include <language/loader.h>
+#include <language/scriptengine.h>
#include <logging/ilogsink.h>
+#include <logging/logger.h>
#include <tools/setupprojectparameters.h>
#include <QtCore/qrandom.h>
@@ -56,27 +57,9 @@ public:
TestLanguage(qbs::ILogSink *logSink, qbs::Settings *settings);
~TestLanguage();
-private:
- qbs::ILogSink *m_logSink;
- qbs::Settings * const m_settings;
- qbs::Internal::Logger m_logger;
- std::unique_ptr<qbs::Internal::ScriptEngine> m_engine;
- qbs::Internal::Loader *loader;
- qbs::Internal::TopLevelProjectPtr project;
- qbs::SetupProjectParameters defaultParameters;
- const QString m_wildcardsTestDirPath;
-
- QHash<QString, qbs::Internal::ResolvedProductPtr> productsFromProject(
- qbs::Internal::ResolvedProjectPtr project);
- qbs::Internal::ResolvedModuleConstPtr findModuleByName(
- qbs::Internal::ResolvedProductPtr product, const QString &name);
- QVariant productPropertyValue(qbs::Internal::ResolvedProductPtr product, QString propertyName);
- void handleInitCleanupDataTags(const char *projectFileName, bool *handled);
-
private slots:
void init();
void initTestCase();
- void cleanupTestCase();
void additionalProductTypes();
void baseProperty();
@@ -95,6 +78,8 @@ private slots:
void disabledSubProject();
void dottedNames_data();
void dottedNames();
+ void duplicateMultiplexValues_data();
+ void duplicateMultiplexValues();
void emptyJsFile();
void enumerateProjectProperties();
void evalErrorInNonPresentModule_data();
@@ -137,6 +122,7 @@ private slots:
void modulePropertiesInGroups();
void modulePropertyOverridesPerProduct();
void moduleScope();
+ void moduleWithProductDependency();
void modules_data();
void modules();
void multiplexedExports();
@@ -185,6 +171,21 @@ private slots:
void wildcards();
private:
+ QHash<QString, qbs::Internal::ResolvedProductPtr> productsFromProject(
+ qbs::Internal::ResolvedProjectPtr project);
+ qbs::Internal::ResolvedModuleConstPtr findModuleByName(
+ qbs::Internal::ResolvedProductPtr product, const QString &name);
+ QVariant productPropertyValue(qbs::Internal::ResolvedProductPtr product, QString propertyName);
+ void handleInitCleanupDataTags(const char *projectFileName, bool *handled);
+ qbs::Internal::TopLevelProjectPtr resolveProject(const char *relProjectFilePath = nullptr);
+
+ qbs::ILogSink * const m_logSink;
+ qbs::Settings * const m_settings;
+ qbs::Internal::Logger m_logger;
+ std::unique_ptr<qbs::Internal::ScriptEngine> m_engine;
+ qbs::Internal::TopLevelProjectPtr project;
+ qbs::SetupProjectParameters defaultParameters;
+ const QString m_wildcardsTestDirPath;
QTemporaryDir m_tempDir;
QRandomGenerator m_rand;
};
diff --git a/tests/auto/shared.h b/tests/auto/shared.h
index 53ff364fb..408594be0 100644
--- a/tests/auto/shared.h
+++ b/tests/auto/shared.h
@@ -158,7 +158,7 @@ inline QByteArray diffText(const QByteArray &actual, const QByteArray &expected)
n++;
}
auto addLines = [&result, &n] (const QList<QByteArray> &lines) {
- for (const QByteArray &line : qAsConst(lines)) {
+ for (const QByteArray &line : std::as_const(lines)) {
result += QStringLiteral("%1: %2\n")
.arg(n)
.arg(QString::fromUtf8(line))