diff options
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 @@ -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 *> ¤tNextChain = 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 *> ¤tNextChain() { 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 ¶meters, Logger &logger) +{ + const QVariant configMap = config.value(key); + if (configMap.isNull()) + return; + overrideProperties(configMap.toMap(), QualifiedId(key), parameters, logger); +} + static const char *valueType(const Value *v) { switch (v->type()) { @@ -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 ¶meters, + Logger &logger); + void overrideProperties( + const QVariantMap &config, const QualifiedId &namePrefix, const SetupProjectParameters ¶meters, 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 ¶meters) -{ - QString projectFilePath = parameters.projectFilePath(); - if (projectFilePath.isEmpty()) - projectFilePath = QDir::currentPath(); - const QFileInfo projectFileInfo(projectFilePath); - if (!projectFileInfo.exists()) - throw ErrorInfo(Tr::tr("Project file '%1' cannot be found.").arg(projectFilePath)); - if (projectFileInfo.isRelative()) - projectFilePath = projectFileInfo.absoluteFilePath(); - if (projectFileInfo.isFile()) { - parameters.setProjectFilePath(projectFilePath); - return; - } - if (!projectFileInfo.isDir()) - throw ErrorInfo(Tr::tr("Project file '%1' has invalid type.").arg(projectFilePath)); - - const QStringList &actualFileNames - = QDir(projectFilePath).entryList(StringConstants::qbsFileWildcards(), QDir::Files); - if (actualFileNames.empty()) { - QString error; - if (parameters.projectFilePath().isEmpty()) - error = Tr::tr("No project file given and none found in current directory.\n"); - else - error = Tr::tr("No project file found in directory '%1'.").arg(projectFilePath); - throw ErrorInfo(error); - } - if (actualFileNames.size() > 1) { - throw ErrorInfo(Tr::tr("More than one project file found in directory '%1'.") - .arg(projectFilePath)); - } - projectFilePath.append(QLatin1Char('/')).append(actualFileNames.front()); - - projectFilePath = QDir::current().filePath(projectFilePath); - projectFilePath = QDir::cleanPath(projectFilePath); - parameters.setProjectFilePath(projectFilePath); -} - -} // namespace Internal -} // namespace qbs diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp deleted file mode 100644 index 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 ¶meters) -{ - TimedActivityLogger moduleLoaderTimer(m_logger, Tr::tr("ModuleLoader"), - parameters.logElapsedTime()); - qCDebug(lcModuleLoader) << "load" << parameters.projectFilePath(); - m_parameters = parameters; - m_modulePrototypes.clear(); - m_modulePrototypeEnabledInfo.clear(); - m_parameterDeclarations.clear(); - m_disabledItems.clear(); - m_reader->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 ¶meters) const - { - check(parameters, QualifiedId()); - } - -private: - void check(const QVariantMap ¶meters, const QualifiedId &moduleName) const - { - for (auto it = parameters.begin(); it != parameters.end(); ++it) { - if (it.value().userType() == QMetaType::QVariantMap) { - check(it.value().toMap(), QualifiedId(moduleName) << it.key()); - } else { - const auto &deps = m_productItem->modules(); - auto m = std::find_if(deps.begin(), deps.end(), - [&moduleName] (const Item::Module &module) { - return module.name == moduleName; - }); - - if (m == deps.end()) { - const QualifiedId fullName = QualifiedId(moduleName) << it.key(); - throw ErrorInfo(Tr::tr("Cannot set parameter '%1', " - "because '%2' does not have a dependency on '%3'.") - .arg(fullName.toString(), m_productName, moduleName.toString()), - m_productItem->location()); - } - - 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 ¤tSearchPaths = 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 ¶mDecls = 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 ¶meters); - -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 ¶meters, JobLimits &jobLimits, - ProjectContext *projectContext); - void gatherProductTypes(ResolvedProduct *product, Item *item); - QVariantMap resolveAdditionalModuleProperties(const Item *group, - const QVariantMap ¤tValues); - void resolveGroup(Item *item, ProjectContext *projectContext); - void resolveGroupFully(Item *item, ProjectContext *projectContext, bool isEnabled); - void resolveShadowProduct(Item *item, ProjectContext *); - void resolveExport(Item *exportItem, ProjectContext *); - std::unique_ptr<ExportedItem> resolveExportChild(const Item *item, - const ExportedModule &module); - void resolveRule(Item *item, ProjectContext *projectContext); - void resolveRuleArtifact(const RulePtr &rule, Item *item); - void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item, - const QStringList &namePrefix, - QualifiedIdSet *seenBindings); - void resolveFileTagger(Item *item, ProjectContext *projectContext); - void resolveJobLimit(Item *item, ProjectContext *projectContext); - void resolveScanner(Item *item, ProjectContext *projectContext); - void resolveProductDependencies(const ProjectContext &projectContext); - void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const; - void applyFileTaggers(const ResolvedProductPtr &product) const; - QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true); - QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors); - QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer, - const QVariantMap &tmplt, bool lookupPrototype, - bool checkErrors); - void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue, - QVariantMap &result, bool checkErrors); - void 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 ¶ms, Logger &logger) + : m_disabledItems(disabledItems) + , m_params(params) + , m_logger(logger) + { } + void operator()(Item *item) { handleItem(item); } + +private: + void handle(JSSourceValue *value) override + { + if (!value->createdByPropertiesBlock()) { + const ErrorInfo error(Tr::tr("Property '%1' is not declared.") + .arg(m_currentName), value->location()); + handlePropertyError(error, m_params, m_logger); + } + } + void handle(ItemValue *value) override + { + if (checkItemValue(value)) + handleItem(value->item()); + } + bool checkItemValue(ItemValue *value) + { + // TODO: Remove once QBS-1030 is fixed. + if (parentItem()->type() == ItemType::Artifact) + return false; + + if (parentItem()->type() == ItemType::Properties) + return false; + + // 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 ¶ms, 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 ¶ms, 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 ¤tSearchPaths = 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 ¶meters) +{ + 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 ¶meters, 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 ¶meters; + 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 ¶meters, 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 ¶meters); + + 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 ¶meters, 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 ¶meters() 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 ¶meters = loaderState.parameters(); + Logger &logger = loaderState.logger(); + context.module->overrideProperties(parameters.overriddenValuesTree(), generalOverrideKey, + parameters, logger); + if (fullName == StringConstants::qbsModule()) { + context.module->overrideProperties(parameters.overriddenValuesTree(), fullName, parameters, + logger); + } + context.module->overrideProperties(parameters.overriddenValuesTree(), perProductOverrideKey, + parameters, logger); +} + +void ModuleInstantiator::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 ¶mDecls = 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 ¶meters) const { check(parameters, QualifiedId()); } + +private: + void check(const QVariantMap ¶meters, const QualifiedId &moduleName) const + { + for (auto it = parameters.begin(); it != parameters.end(); ++it) { + if (it.value().userType() == QMetaType::QVariantMap) { + check(it.value().toMap(), QualifiedId(moduleName) << it.key()); + } else { + const auto &deps = m_productItem->modules(); + auto m = std::find_if(deps.begin(), deps.end(), + [&moduleName] (const Item::Module &module) { + return module.name == moduleName; + }); + + if (m == deps.end()) { + const QualifiedId fullName = QualifiedId(moduleName) << it.key(); + throw ErrorInfo(Tr::tr("Cannot set parameter '%1', " + "because '%2' does not have a dependency on '%3'.") + .arg(fullName.toString(), m_productName, moduleName.toString()), + m_productItem->location()); + } + + const auto decls = m_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 ¤tHead, 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 ¤tHead, 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 ¶meters = loaderState.parameters(); + Evaluator &evaluator = loaderState.evaluator(); + Logger &logger = loaderState.logger(); + ItemReader &itemReader = loaderState.itemReader(); + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + + auto p = std::make_unique<ProjectContext>(); + auto &projectContext = *p; + projectContext.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 ¶meters = 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 ¶meters = loaderState.parameters(); + Evaluator &evaluator = loaderState.evaluator(); + Logger &logger = loaderState.logger(); + ItemReader &itemReader = loaderState.itemReader(); + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + + qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath(); + + Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject); + if (!topLevelProject.checkItemCondition(projectItem, evaluator)) + return; + if (propertiesItem) { + propertiesItem->setScope(projectItem); + if (!topLevelProject.checkItemCondition(propertiesItem, evaluator)) + return; + } + + Item *loadedItem = nullptr; + QString subProjectFilePath; + try { + const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); + const QString relativeFilePath + = evaluator.stringValue(projectItem, StringConstants::filePathProperty()); + subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); + if (referencedFilePaths.contains(subProjectFilePath)) + throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") + .arg(relativeFilePath), projectItem->location()); + loadedItem = itemReader.setupItemFromFile(subProjectFilePath, projectItem->location()); + } catch (const ErrorInfo &error) { + if (parameters.productErrorMode() == ErrorHandlingMode::Strict) + throw; + logger.printWarning(error); + return; + } + + loadedItem = itemReader.wrapInProjectIfNecessary(loadedItem); + const bool inheritProperties = evaluator.boolValue( + projectItem, StringConstants::inheritPropertiesProperty()); + + if (inheritProperties) + copyProperties(projectItem->parent(), loadedItem); + if (propertiesItem) { + const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); + for (auto it = overriddenProperties.begin(); it != overriddenProperties.end(); ++it) + loadedItem->setProperty(it.key(), it.value()); + } + + Item::addChild(projectItem, loadedItem); + projectItem->setScope(projectContext.scope); + handleProject(loadedItem, 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 ¶meters = loaderState.parameters(); + Evaluator &evaluator = loaderState.evaluator(); + Logger &logger = loaderState.logger(); + TopLevelProjectContext &topLevelProject = loaderState.topLevelProject(); + + std::vector<Item *> exportItems; + QList<Item *> children = productContext.item->children(); + + const auto isExport = [](Item *item) { return item->type() == ItemType::Export; }; + std::copy_if(children.cbegin(), children.cend(), std::back_inserter(exportItems), isExport); + qbs::Internal::removeIf(children, isExport); + + // Note that we do not return if there are no Export items: The "merged" item becomes the + // "product module", which always needs to exist, regardless of whether the product sources + // actually contain an Export item or not. + if (!exportItems.empty()) + productContext.item->setChildren(children); + + Item *merged = Item::create(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 ¶meters, JobLimits &jobLimits, + ResolverProjectContext *projectContext); + void gatherProductTypes(ResolvedProduct *product, Item *item); + QVariantMap resolveAdditionalModuleProperties(const Item *group, + const QVariantMap ¤tValues); + 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 ¶meters) { - 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 ¶meters, JobLimits &jobLimits, - ProjectContext *projectContext) +void ProjectResolver::Private::resolveModule( + const QualifiedId &moduleName, Item *item, bool isProduct, const QVariantMap ¶meters, + 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 ¤tValues) +QVariantMap ProjectResolver::Private::resolveAdditionalModuleProperties( + const Item *group, const QVariantMap ¤tValues) { // 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 ¶meters); - - static void setupProjectFilePath(SetupProjectParameters ¶meters); + TopLevelProjectPtr resolve(const SetupProjectParameters ¶meters); 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 ¶meters, 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 ¶meters, 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 ¶meters, 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 ¤tNamePart, const QStringList &restOfName, const QVariant &value) { Node *currentNode = nullptr; - for (Node * const n : qAsConst(parentNode->children)) { + for (Node * const n : std::as_const(parentNode->children)) { if (n->name == currentNamePart) { currentNode = n; break; @@ -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)) |