diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2018-06-08 14:55:29 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2018-06-08 15:13:47 +0200 |
commit | 073fda0ab536b5610ff1b9191db582791552509e (patch) | |
tree | 16f0bbacc116e93778e9490ba2c2d9efff738d35 | |
parent | 9349866b37118db9179d1f0689e872ca1260f040 (diff) | |
parent | 5f71b2220f9ff6838799c407972309bff1e8fc96 (diff) |
Merge 1.12 into master
Change-Id: I0c914674c0728a7f7bc70fd9608914b95ef55a30
42 files changed, 1048 insertions, 28 deletions
diff --git a/changelogs/changes-1.12.0.md b/changelogs/changes-1.12.0.md index f84a64470..6c88d35b7 100644 --- a/changelogs/changes-1.12.0.md +++ b/changelogs/changes-1.12.0.md @@ -1,5 +1,7 @@ # General * Added new module `Exporter.qbs` for creating qbs modules from products. +* Added new module `Exporter.pkgconfig` for creating pkg-config metadata files. +* Introduced the concept of system-level qbs settings. * Added a Makefile generator. * All command descriptions now contain the product name. diff --git a/doc/man/qbs.1 b/doc/man/qbs.1 index cf5d5d767..32c112886 100644 --- a/doc/man/qbs.1 +++ b/doc/man/qbs.1 @@ -1,12 +1,12 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.5. -.TH QBS "1" "October 2017" "qbs 1.11.0" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH QBS "1" "June 2018" "qbs 1.12.0" "User Commands" .SH NAME qbs \- the Qbs build tool .SH SYNOPSIS .B qbs [\fI\,command\/\fR] [\fI\,command parameters\/\fR] .SH DESCRIPTION -Qbs 1.11.0, a cross\-platform build tool. +Qbs 1.12.0, a cross\-platform build tool. .SS "Built-in commands:" .TP build diff --git a/doc/reference/items/language/export.qdoc b/doc/reference/items/language/export.qdoc index 3c89117e9..d8ed4c33d 100644 --- a/doc/reference/items/language/export.qdoc +++ b/doc/reference/items/language/export.qdoc @@ -91,5 +91,7 @@ \endcode \defaultvalue \c undefined + \see Exporter.qbs + \see Exporter.pkgconfig \since 1.12 */ diff --git a/doc/reference/modules/exporter-pkgconfig-module.qdoc b/doc/reference/modules/exporter-pkgconfig-module.qdoc new file mode 100644 index 000000000..9489b4622 --- /dev/null +++ b/doc/reference/modules/exporter-pkgconfig-module.qdoc @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \contentspage index.html + \qmltype Exporter.pkgconfig + \inqmlmodule QbsModules + \since Qbs 1.12 + + \brief Provides support for generating pkg-config files. + + The \c Exporter.pkgconfig module contains the properties and rules to create a + \l{https://www.freedesktop.org/wiki/Software/pkg-config}{pkg-config} + metadata (\c{.pc}) file for a \l Product. + + By default, \QBS will attempt to derive some of the \c {.pc} file's contents from + the product's \l Export item. This behavior can be suppressed by setting the + \l autoDetect property to \c false. + + \section2 Relevant File Tags + \target filetags-exporter-pkgconfig + + \table + \header + \li Tag + \li Since + \li Description + \row + \li \c{"Exporter.pkgconfig.pc"} + \li 1.12.0 + \li This tag is attached to the generated \c{.pc} file. + \endtable +*/ + +/*! + \qmlproperty bool Exporter.pkgconfig::autoDetect + + If this property is enabled, then \QBS will try to derive various \c {.pc} file + entries from the contents of the product's \l Export item, including the dependencies + declared therein. Values for these fields can still be explicitly provided via the + respective properties, in which case they will be concatenated with the auto-detected ones. + If an exported dependency is known to correspond to a pkg-config module (either by + pulling in the \c{Exporter.pkgconfig} module or by appearing in the \l requiresEntry + property), it will end up in the \c Requires field of the \c{.pc} file, otherwise + its exported \l cpp properties will be collected for use in the \c Cflags and \c Libs fields. + The \l excludedDependencies property can be used to ignore specific products altogether. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::cflagsEntry + + The value of the \c Cflags field in the \c {.pc} file. + + If \l autoDetect is enabled, then this value will be appended to the flags derived from + the product's \l Export item and the dependencies declared therein. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::conflictsEntry + + The value of the \c Conflicts field in the \c {.pc} file. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::excludedDependencies + + If \l autoDetect is enabled, the entries of this property will be matched against + the product's exported dependencies. In case such a dependency's name is present in the array, + \QBS will not traverse that dependency to collect entries for the + \c Cflags, \c Libs, \c Requires and \c {Requires.private} fields. + + This list must not contain any values that are present in \l requiresEntry. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::fileName + + The file name of the generated pkg-config metadata file. + + \defaultvalue \c {product.targetName + ".pc"} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::libsEntry + + The value of the \c Libs field in the \c {.pc} file. + + If \l autoDetect is enabled, then this value will be appended to the flags derived from + the product's \l Export item and the dependencies declared therein. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::libsPrivateEntry + + The value of the \c Libs.Private field in the \c {.pc} file. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::nameEntry + + The value of the \c Name field in the \c {.pc} file. + + \defaultvalue \c {product.name} +*/ + + +/*! + \qmlproperty stringList Exporter.pkgconfig::requiresEntry + + The value of the \c Requires field in the \c {.pc} file. + + If \l autoDetect is enabled, then those of the product's exported dependencies + that pull in the \c {Exporter.pkgconfig} module will also end up in the + \c Requires field, provided they are not listed in \l excludedDependencies. + + If an exported dependency matches an entry of this array, \QBS will not traverse that + dependency to gather \l cpp properties for use in the \l cflagsEntry and \l libsEntry values, + as pkg-config takes care of that itself. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::requiresPrivateEntry + + The value of the \c Requires.private field in the \c {.pc} file. + + If \l autoDetect is enabled, then those of the product's non-exported dependencies + that pull in the \c {Exporter.pkgconfig} module will also end up in the + \c Requires.private field, provided they are not listed in \l excludedDependencies. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty var Exporter.pkgconfig::transformFunction + + A function with the signature + \c {function(product, moduleName, propertyName, value)}. + This can be useful to "fine-tune" property values if \l autoDetect is enabled, in case + they need amending for the purpose of pkg-config. + The \c product parameter represents the exporting product, the remaining parameters describe + the module property. The modified value of the module property shall be returned. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::urlEntry + + The value of the \c URL field in the \c {.pc} file. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::versionEntry + + The value of the \c Version field in the \c {.pc} file. + + \defaultvalue \c {product.version} +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::customVariables + + Use this property to add arbitrary variable assignments into the \c .pc file. + The property is a map that will produce one assignment per entry. + The keys and values of the map represent the left-hand sides and right-hand + sides of these assignments, respectively. The values are strings that will + be written into the file verbatim. + + \nodefaultvalue +*/ diff --git a/qbs-resources/imports/QbsLibrary.qbs b/qbs-resources/imports/QbsLibrary.qbs index f8bc70580..69e107871 100644 --- a/qbs-resources/imports/QbsLibrary.qbs +++ b/qbs-resources/imports/QbsLibrary.qbs @@ -19,10 +19,14 @@ QbsProduct { property string visibilityType: staticBuild ? "static" : "dynamic" property string headerInstallPrefix: "/include/qbs" property bool hasExporter: Utilities.versionCompare(qbs.version, "1.12") >= 0 + property bool generatePkgConfigFile: qbsbuildconfig.generatePkgConfigFiles && hasExporter property bool generateQbsModule: install && qbsbuildconfig.generateQbsModules && hasExporter property bool staticBuild: Qt.core.staticBuild || qbsbuildconfig.staticBuild property stringList libType: [staticBuild ? "staticlibrary" : "dynamiclibrary"] + + Depends { name: "Exporter.pkgconfig"; condition: generatePkgConfigFile } Depends { name: "Exporter.qbs"; condition: generateQbsModule } + Group { fileTagsFilter: libType.concat("dynamiclibrary_symlink") .concat(qbs.buildVariant === "debug" ? ["debuginfo_dll"] : []) @@ -37,6 +41,11 @@ QbsProduct { qbs.installDir: qbsbuildconfig.importLibInstallDir } Group { + fileTagsFilter: "Exporter.pkgconfig.pc" + qbs.install: install + qbs.installDir: qbsbuildconfig.pkgConfigInstallDir + } + Group { fileTagsFilter: "Exporter.qbs.module" qbs.install: install qbs.installDir: FileInfo.joinPaths(qbsbuildconfig.qbsModulesBaseDir, product.name) diff --git a/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs b/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs index 50a5ea79e..0627a5cf9 100644 --- a/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs +++ b/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs @@ -19,8 +19,11 @@ Module { property bool installManPage: qbs.targetOS.contains("unix") property bool installHtml: true property bool installQch: false + property bool generatePkgConfigFiles: installApiHeaders && qbs.targetOS.contains("unix") + && !qbs.targetOS.contains("darwin") property bool generateQbsModules: installApiHeaders property string docInstallDir: "share/doc/qbs/html" + property string pkgConfigInstallDir: FileInfo.joinPaths(libDirName, "pkgconfig") property string qbsModulesBaseDir: FileInfo.joinPaths(libDirName, "qbs", "modules") property string relativeLibexecPath: "../" + libexecInstallDir property string relativePluginsPath: "../" + libDirName diff --git a/share/qbs/modules/Exporter/pkgconfig/pkgconfig.js b/share/qbs/modules/Exporter/pkgconfig/pkgconfig.js new file mode 100644 index 000000000..a3109d61d --- /dev/null +++ b/share/qbs/modules/Exporter/pkgconfig/pkgconfig.js @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing +** +** This file is part of Qbs. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); + +function quote(value) +{ + if (value.contains(" ") || value.contains("'") || value.contains('"')) { + return '"' + value.replace(/(["'\\])/g, "\\$1") + '"'; + } + return value; +} + +function writeEntry(product, file, key, propertyName, required, additionalValues) +{ + var value = product.Exporter.pkgconfig[propertyName]; + if (additionalValues && additionalValues.length > 0) + value = (value || []).concat(additionalValues); + var valueIsNotEmpty = value && (!Array.isArray(value) || value.length > 0); + if (valueIsNotEmpty) { + if (Array.isArray(value)) + value = value.join(' '); + file.writeLine(key + ": " + value); + } else if (required) { + throw "Failure creating " + FileInfo.fileName(file.filePath()) + ": The entry '" + key + + "' is required, but property Exporter.pkgconfig." + + propertyName + " is not set."; + } +} + +function collectAutodetectedData(topLevelProduct) +{ + var data = { + libs: [], + cflags: [], + requires: [], + requiresPrivate: [] + }; + if (!topLevelProduct.Exporter.pkgconfig.autoDetect) + return data; + + var excludedDeps = topLevelProduct.Exporter.pkgconfig.excludedDependencies || []; + var explicitRequires = topLevelProduct.Exporter.pkgconfig.requiresEntry || []; + var explicitRequiresPrivate = topLevelProduct.Exporter.pkgconfig.requiresPrivateEntry || []; + + var transformFunc = topLevelProduct.Exporter.pkgconfig.transformFunction; + + // Make use of the "prefix" convenience variable if applicable. + function quoteAndPrefixify(value) + { + var quotedValue = quote(value); + var installPrefix = topLevelProduct.qbs.installPrefix || ""; + if (!topLevelProduct.Exporter.pkgconfig._usePrefix || typeof value !== "string" + || !value.startsWith(installPrefix) + || (value.length > installPrefix.length && value[installPrefix.length] !== '/')) { + return quotedValue; + } + return quotedValue.replace(product.qbs.installPrefix, "${prefix}"); + } + + function transformedValue(product, moduleName, propertyName) + { + var originalValue = product.exports[moduleName][propertyName]; + var value = transformFunc + ? eval("(" + transformFunc + ")(product, moduleName, propertyName, originalValue)") + : originalValue; + if (Array.isArray(value)) + value.forEach(function(v, i, a) { a[i] = quoteAndPrefixify(v); }); + else if (value) + value = quoteAndPrefixify(value); + return value; + } + + function collectLibs(productOrModule) + { + var libs = []; + var libArtifacts; + var isProduct = !productOrModule.present; + var considerDynamicLibs = !isProduct || (productOrModule.type + && productOrModule.type.contains("dynamiclibrary")); + if (considerDynamicLibs) { + libArtifacts = productOrModule.artifacts.dynamiclibrary; + } else { + var considerStaticLibs = !isProduct || (productOrModule.type + && productOrModule.type.contains("staticlibrary")); + if (considerStaticLibs) + libArtifacts = productOrModule.artifacts.staticlibrary; + } + for (var i = 0; i < (libArtifacts || []).length; ++i) { + var libArtifact = libArtifacts[i]; + if (libArtifact.qbs.install) { + var installDir = FileInfo.path(ModUtils.artifactInstalledFilePath(libArtifact)); + installDir = installDir.slice(libArtifact.qbs.installRoot.length); + libs.push("-L" + quoteAndPrefixify(FileInfo.cleanPath(installDir)), + "-l" + quote(productOrModule.targetName)); + } + } + if (!productOrModule.exports.cpp) + return libs; + var libPaths = transformedValue(productOrModule, "cpp", "libraryPaths"); + if (libPaths) + libs.push.apply(libs, libPaths.map(function(p) { return "-L" + p; })); + function libNamesToLibEntries(libNames) { + return libNames.map(function(libName) { return "-l" + libName; }); + }; + var dlls = transformedValue(productOrModule, "cpp", "dynamicLibraries"); + if (dlls) + libs.push.apply(libs, libNamesToLibEntries(dlls)); + var staticLibs = transformedValue(productOrModule, "cpp", "staticLibraries"); + if (staticLibs) + libs.push.apply(libs, libNamesToLibEntries(staticLibs)); + var lFlags = transformedValue(productOrModule, "cpp", "linkerFlags"); + if (lFlags) + libs.push.apply(libs, lFlags); + lFlags = transformedValue(productOrModule, "cpp", "driverFlags"); + if (lFlags) + libs.push.apply(libs, lFlags); + lFlags = transformedValue(productOrModule, "cpp", "driverLinkerFlags"); + if (lFlags) + libs.push.apply(libs, lFlags); + return libs; + } + + function collectCFlags(productOrModule) + { + if (!productOrModule.exports.cpp) + return []; + var flags = []; + var defs = transformedValue(productOrModule, "cpp", "defines"); + if (defs) + flags.push.apply(flags, defs.map(function(d) { return "-D" + d; })); + var incPaths = transformedValue(productOrModule, "cpp", "includePaths"); + if (incPaths) + flags.push.apply(flags, incPaths.map(function(p) { return "-I" + p; })); + var cflags = transformedValue(productOrModule, "cpp", "commonCompilerFlags"); + if (cflags) + flags.push.apply(flags, cflags); + cflags = transformedValue(productOrModule, "cpp", "driverFlags"); + if (cflags) + flags.push.apply(flags, cflags); + cflags = transformedValue(productOrModule, "cpp", "cxxFlags") + || transformedValue(productOrModule, "cpp", "cFlags"); + if (cflags) + flags.push.apply(flags, cflags); + return flags; + } + + function collectAutodetectedDataRecursive(productOrModule, privateContext) + { + if (!privateContext) { + data.libs.push.apply(data.libs, collectLibs(productOrModule)); + data.cflags.push.apply(data.cflags, collectCFlags(productOrModule)); + } + var exportedDeps = productOrModule.exports ? productOrModule.exports.dependencies : []; + var exportedDepNames = []; + var privateDeps = []; + for (var i = 0; i < exportedDeps.length; ++i) + exportedDepNames.push(exportedDeps[i].name); + for (i = 0; i < (productOrModule.dependencies || []).length; ++i) { + var dep = productOrModule.dependencies[i]; + if (exportedDepNames.contains(dep.name)) + continue; + privateDeps.push(dep); + } + + function gatherData(dep) { + if (dep.name === "Exporter.pkgconfig") + return; + var depHasPkgConfig = dep.Exporter && dep.Exporter.pkgconfig; + if (depHasPkgConfig) { + var entry = FileInfo.completeBaseName(dep.Exporter.pkgconfig.fileName); + if (excludedDeps.contains(entry)) + return; + if (isPrivateDep && !data.requiresPrivate.contains(entry) + && !explicitRequiresPrivate.contains(entry)) { + data.requiresPrivate.push(entry); + } + if (!isPrivateDep && !data.requires.contains(entry) + && !explicitRequires.contains(entry)) { + data.requires.push(entry); + } + } else { + if (excludedDeps.contains(dep.name)) + return; + if (isPrivateDep && explicitRequiresPrivate.contains(dep.name)) + return; + if (!isPrivateDep && explicitRequires.contains(dep.name)) + return; + collectAutodetectedDataRecursive(dep, isPrivateDep); + } + } + var isPrivateDep = privateContext; + exportedDeps.forEach(gatherData); + isPrivateDep = true; + privateDeps.forEach(gatherData); + } + + collectAutodetectedDataRecursive(topLevelProduct, false); + return data; +} diff --git a/share/qbs/modules/Exporter/pkgconfig/pkgconfig.qbs b/share/qbs/modules/Exporter/pkgconfig/pkgconfig.qbs new file mode 100644 index 000000000..5fbda2bf4 --- /dev/null +++ b/share/qbs/modules/Exporter/pkgconfig/pkgconfig.qbs @@ -0,0 +1,91 @@ +import qbs +import qbs.FileInfo +import qbs.TextFile + +import "pkgconfig.js" as HelperFunctions + +Module { + property string fileName: product.targetName + ".pc" + property bool autoDetect: true + property var transformFunction // function(product, moduleName, propertyName, valueElement) + property stringList excludedDependencies + + property string nameEntry: product.name + property string descriptionEntry: product.name + property string versionEntry: product.version + property string urlEntry + property stringList cflagsEntry: [] + property stringList libsEntry: [] + property stringList libsPrivateEntry: [] + property stringList requiresEntry: [] + property stringList requiresPrivateEntry: [] + property stringList conflictsEntry: [] + + property var customVariables + + property bool _usePrefix: autoDetect && qbs.installPrefix + + additionalProductTypes: ["Exporter.pkgconfig.pc"] + + Rule { + multiplex: true + requiresInputs: false + + // Make sure all relevant library artifacts have been created by the time we run. + inputsFromDependencies: autoDetect + ? ["Exporter.pkgconfig.pc", "staticlibrary", "dynamiclibrary"] + : [] + inputs: { + if (!product.Exporter.pkgconfig.autoDetect) + return undefined; + if (product.type.contains("staticlibrary")) + return ["staticlibrary"]; + if (product.type.contains("dynamiclibrary")) + return ["dynamiclibrary"]; + } + + Artifact { + filePath: product.Exporter.pkgconfig.fileName + fileTags: ["Exporter.pkgconfig.pc"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + if (product.Exporter.pkgconfig._usePrefix) + f.writeLine("prefix=" + product.qbs.installPrefix + "\n"); + var customVariables = product.Exporter.pkgconfig.customVariables; + if (customVariables) { + for (var customVar in customVariables) + f.writeLine(customVar + "=" + customVariables[customVar]); + f.writeLine(""); + } + var autoDetectedData = HelperFunctions.collectAutodetectedData(product); + HelperFunctions.writeEntry(product, f, "Name", "nameEntry", true); + HelperFunctions.writeEntry(product, f, "Description", "descriptionEntry", true); + HelperFunctions.writeEntry(product, f, "Version", "versionEntry", true); + HelperFunctions.writeEntry(product, f, "URL", "urlEntry"); + HelperFunctions.writeEntry(product, f, "Cflags", "cflagsEntry", false, + autoDetectedData.cflags); + HelperFunctions.writeEntry(product, f, "Libs", "libsEntry", false, + autoDetectedData.libs); + HelperFunctions.writeEntry(product, f, "Libs.private", "libsPrivateEntry"); + HelperFunctions.writeEntry(product, f, "Requires", "requiresEntry", false, + autoDetectedData.requires); + HelperFunctions.writeEntry(product, f, "Requires.private", "requiresPrivateEntry", + false, autoDetectedData.requiresPrivate); + HelperFunctions.writeEntry(product, f, "Conflicts", "conflictsEntry"); + }; + return [cmd]; + } + } + + validate: { + if (requiresEntry && excludedDependencies + && requiresEntry.containsAny(excludedDependencies)) { + throw "The contents of Export.pkgconfig.requiresEntry and " + + "Export.pkgconfig.excludedDependencies must not overlap."; + } + } +} diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs index 5676d62e0..792e8ef4c 100644 --- a/share/qbs/modules/cpp/GenericGCC.qbs +++ b/share/qbs/modules/cpp/GenericGCC.qbs @@ -456,7 +456,7 @@ CppModule { condition: product.cpp.shouldLink multiplex: true inputs: ["obj", "linkerscript"] - inputsFromDependencies: ["dynamiclibrary", "staticlibrary"] + inputsFromDependencies: ["dynamiclibrary_symbols", "staticlibrary"] outputFileTags: ["bundle.input", "staticlibrary", "c_staticlibrary", "cpp_staticlibrary"] outputArtifacts: { diff --git a/src/app/qbs-setup-qt/setupqt.cpp b/src/app/qbs-setup-qt/setupqt.cpp index a611a8c07..097c9381a 100644 --- a/src/app/qbs-setup-qt/setupqt.cpp +++ b/src/app/qbs-setup-qt/setupqt.cpp @@ -392,7 +392,14 @@ static Match compatibility(const EnhancedQtEnvironment &env, const Profile &tool // because it's especially important for this toolchain const Version compilerVersion = Version::fromString( toolchainProfile.value(QLatin1String("cpp.compilerVersion")).toString()); - if (env.msvcVersion.majorVersion() != compilerVersion.majorVersion() + + static const Version vs2017Version{19, 10}; + if (env.msvcVersion >= vs2017Version) { + if (env.msvcVersion.majorVersion() != compilerVersion.majorVersion() + || compilerVersion < vs2017Version) { + return MatchNone; + } + } else if (env.msvcVersion.majorVersion() != compilerVersion.majorVersion() || env.msvcVersion.minorVersion() != compilerVersion.minorVersion()) { return MatchNone; } diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp index 24f484ad2..8713b9b41 100644 --- a/src/lib/corelib/buildgraph/executor.cpp +++ b/src/lib/corelib/buildgraph/executor.cpp @@ -771,8 +771,7 @@ void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0) return; qCDebug(lcBuildGraph) << "Attempting to rescue data of artifact" << artifact->fileName(); - typedef std::pair<Artifact *, bool> ChildArtifactData; - QList<ChildArtifactData> childrenToConnect; + std::vector<Artifact *> childrenToConnect; bool canRescue = artifact->transformer->commands == rad.commands; if (canRescue) { ResolvedProductPtr pseudoProduct = ResolvedProduct::create(); @@ -796,10 +795,15 @@ void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0) removeGeneratedArtifactFromDisk(cd.childFilePath, m_logger); } } - // TODO: Shouldn't addedByScanner always be true here? Otherwise the child would be - // in the list already, no? + if (!cd.addedByScanner) { + // If an artifact has disappeared from the list of children, the commands + // might need to run again. + canRescue = false; + qCDebug(lcBuildGraph) << "Former child artifact" << cd.childFilePath << + "is no longer in the list of children"; + } if (canRescue) - childrenToConnect.push_back({child, cd.addedByScanner}); + childrenToConnect.push_back(child); } for (const QString &depPath : rad.fileDependencies) { const QList<FileResourceBase *> depList = m_project->buildData->lookupFiles(depPath); @@ -865,10 +869,9 @@ void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = 0) artifact->knownOutOfDate = artifact->knownOutOfDate || rad.knownOutOfDate; if (childrenAdded && !childrenToConnect.empty()) *childrenAdded = true; - for (const ChildArtifactData &cad : qAsConst(childrenToConnect)) { - safeConnect(artifact, cad.first); - if (cad.second) - artifact->childrenAddedByScanner << cad.first; + for (Artifact * const child : childrenToConnect) { + safeConnect(artifact, child); + artifact->childrenAddedByScanner << child; } qCDebug(lcBuildGraph) << "Data was rescued."; } else { diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs index fd25d30e0..08733b624 100644 --- a/src/lib/corelib/corelib.qbs +++ b/src/lib/corelib/corelib.qbs @@ -17,7 +17,9 @@ QbsLibrary { Depends { condition: qbsbuildconfig.enableProjectFileUpdates; name: "Qt.gui" } Depends { condition: staticBuild; productTypes: ["qbsplugin"] } name: "qbscore" - cpp.includePaths: base.concat([ + property stringList bundledQtScriptIncludes: qbsbuildconfig.useBundledQtScript + || !Qt.script.present ? qbsscriptengine.includePaths : [] + cpp.includePaths: base.concat(bundledQtScriptIncludes).concat([ ".", "../.." // for the plugin headers ]) diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp index 933c49abc..df47e36ea 100644 --- a/src/lib/corelib/language/moduleloader.cpp +++ b/src/lib/corelib/language/moduleloader.cpp @@ -581,6 +581,8 @@ void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *p for (ProductContext * const p : productSorter.sortedProducts()) { try { handleProduct(p); + if (p->name.startsWith(shadowProductPrefix())) + tlp.probes << p->info.probes; } catch (const ErrorInfo &err) { handleProductError(err, p); } @@ -3418,7 +3420,8 @@ void ModuleLoader::resolveProbe(ProductContext *productContext, Item *parent, It const bool condition = m_evaluator->boolValue(probe, StringConstants::conditionProperty()); const QString &sourceCode = configureScript->sourceCode().toString(); ProbeConstPtr resolvedProbe; - if (parent->type() == ItemType::Project) { + if (parent->type() == ItemType::Project + || productContext->name.startsWith(shadowProductPrefix())) { resolvedProbe = findOldProjectProbe(probeId, condition, initialProperties, sourceCode); } else { const QString &uniqueProductName = productContext->uniqueName(); diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp index b7535c2eb..ff74f845a 100644 --- a/src/lib/corelib/language/projectresolver.cpp +++ b/src/lib/corelib/language/projectresolver.cpp @@ -1682,7 +1682,21 @@ void ProjectResolver::evaluateProperty(const Item *item, const QString &propName // NOTE: Loses type information if scriptValue.isUndefined == true, // as such QScriptValues become invalid QVariants. - QVariant v = scriptValue.isFunction() ? scriptValue.toString() : scriptValue.toVariant(); + QVariant v; + if (scriptValue.isFunction()) { + v = scriptValue.toString(); + } else { + v = scriptValue.toVariant(); + QVariantMap m = v.toMap(); + if (m.contains(StringConstants::importScopeNamePropertyInternal())) { + QVariantMap tmp = m; + m = scriptValue.prototype().toVariant().toMap(); + for (auto it = tmp.begin(); it != tmp.end(); ++it) + m.insert(it.key(), it.value()); + v = m; + } + } + if (pd.type() == PropertyDeclaration::Path && v.isValid()) { v = v.toString(); } else if (pd.type() == PropertyDeclaration::PathList diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp index 568a927b2..e983ae945 100644 --- a/src/lib/corelib/language/scriptengine.cpp +++ b/src/lib/corelib/language/scriptengine.cpp @@ -152,6 +152,7 @@ void ScriptEngine::import(const FileContextBaseConstPtr &fileCtx, QScriptValue & if (m_observeMode == ObserveMode::Enabled) { for (QScriptValue &sv : m_requireResults) observeImport(sv); + m_requireResults.clear(); } m_currentDirPathStack.pop(); @@ -507,8 +508,7 @@ QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qt if (!values.empty()) { const QScriptValue mergedValue = mergeExtensionObjects(values); - if (engine->m_observeMode == ObserveMode::Enabled) - engine->m_requireResults.push_back(mergedValue); + engine->m_requireResults.push_back(mergedValue); engine->m_filePathsPerImport[mergedValue.objectId()] = filePaths; return mergedValue; } @@ -533,8 +533,7 @@ QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qt const QString scopeName = scopeNamePrefix + QString::number(qHash(filePath), 16); result.setProperty(StringConstants::importScopeNamePropertyInternal(), scopeName); context->thisObject().setProperty(scopeName, result); - if (engine->m_observeMode == ObserveMode::Enabled) - engine->m_requireResults.push_back(result); + engine->m_requireResults.push_back(result); engine->m_filePathsPerImport[result.objectId()] = { filePath }; } catch (const ErrorInfo &e) { result = context->throwError(e.toString()); diff --git a/src/lib/qtprofilesetup/templates/qmlcache.qbs b/src/lib/qtprofilesetup/templates/qmlcache.qbs index 246486c53..9111eb500 100644 --- a/src/lib/qtprofilesetup/templates/qmlcache.qbs +++ b/src/lib/qtprofilesetup/templates/qmlcache.qbs @@ -1,14 +1,22 @@ +import qbs.File import qbs.FileInfo import qbs.Process +import qbs.Utilities Module { additionalProductTypes: ["qt.qml.qmlc", "qt.qml.jsc"] - validate: qmlcachegenProbe.found + validate: { + if (!qmlcachegenProbe.found) + throw "qmlcachegen unsupported for this target"; + } property string qmlCacheGenPath: FileInfo.joinPaths(Qt.core.binPath, "qmlcachegen") + (qbs.hostOS.contains("windows") ? ".exe" : "") + property bool supportsAllArchitectures: Utilities.versionCompare(Qt.core.version, "5.11") >= 0 property string installDir readonly property stringList _targetArgs: { + if (supportsAllArchitectures) + return []; function translateArch(arch) { if (arch === "x86") return "i386"; @@ -24,15 +32,25 @@ Module { Depends { name: "Qt.core" } Probe { id: qmlcachegenProbe + + property string arch: qbs.architecture + property string _qmlCacheGenPath: qmlCacheGenPath + property stringList targetArgs: _targetArgs + property bool _supportsAllArchitectures: supportsAllArchitectures + configure: { + if (_supportsAllArchitectures) { + found = File.exists(_qmlCacheGenPath); + return; + } var process = new Process(); found = false; try { - found = process.exec(qmlCacheGenPath, - _targetArgs.concat("--check-if-supported")) == 0; + found = process.exec(_qmlCacheGenPath, + targetArgs.concat("--check-if-supported")) == 0; if (!found) { var msg = "QML cache generation was requested but is unsupported on " - + "architecture '" + qbs.architecture + "'."; + + "architecture '" + arch + "'."; console.warn(msg); } } finally { diff --git a/src/lib/scriptengine/include/QtScript/qtscriptglobal.h b/src/lib/scriptengine/include/QtScript/qtscriptglobal.h new file mode 100644 index 000000000..8b1e09bb1 --- /dev/null +++ b/src/lib/scriptengine/include/QtScript/qtscriptglobal.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_QTSCRIPTGLOBAL_H +#define QBS_QTSCRIPTGLOBAL_H + +#define Q_SCRIPT_EXPORT +#define Q_SCRIPTTOOLS_EXPORT + +#endif // include guard diff --git a/src/lib/scriptengine/scriptengine.pro b/src/lib/scriptengine/scriptengine.pro index f5d4ad590..c5ad5f07a 100644 --- a/src/lib/scriptengine/scriptengine.pro +++ b/src/lib/scriptengine/scriptengine.pro @@ -61,6 +61,7 @@ DEFINES += JS_NO_EXPORT } INCLUDEPATH += \ + $$PWD/include \ $$OUT_PWD/include \ $$OUT_PWD/include/QtScript/$$[QT_VERSION]/QtScript \ $$PWD/../../shared/qtscript/src/script \ diff --git a/src/lib/scriptengine/scriptengine.qbs b/src/lib/scriptengine/scriptengine.qbs index c92bc1954..77e6ef7d2 100644 --- a/src/lib/scriptengine/scriptengine.qbs +++ b/src/lib/scriptengine/scriptengine.qbs @@ -17,12 +17,18 @@ Project { type: ["staticlibrary"] name: "qbsscriptengine" + generatePkgConfigFile: false + generateQbsModule: false + property bool useSystemMalloc: !qbs.targetOS.contains("macos") && !qbs.targetOS.contains("unix") property string qtscriptPath: "../../shared/qtscript/src/" cpp.includePaths: { - var result = base.concat(["."]); + var result = base.concat( + ".", + "include" + ); var jscBaseDir = qtscriptPath + "3rdparty/javascriptcore"; result.push(jscBaseDir); @@ -355,7 +361,8 @@ Project { Export { Depends { name: "QtScriptFwdHeaders" } Depends { name: "cpp" } - cpp.includePaths: QtScriptFwdHeaders.publicIncludePaths + property stringList includePaths: [product.sourceDirectory + "/include"] + .concat(QtScriptFwdHeaders.publicIncludePaths) Properties { condition: qbs.targetOS.contains("unix") cpp.dynamicLibraries: base.concat(["pthread"]) diff --git a/src/lib/scriptengine/use_scriptengine.pri b/src/lib/scriptengine/use_scriptengine.pri index 296b6e416..6450e171f 100644 --- a/src/lib/scriptengine/use_scriptengine.pri +++ b/src/lib/scriptengine/use_scriptengine.pri @@ -7,4 +7,5 @@ } INCLUDEPATH += \ + $$PWD/include \ $$shadowed($$PWD/include) diff --git a/tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs b/tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs index db9dcae0c..397c2a691 100644 --- a/tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs +++ b/tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs @@ -1,4 +1,5 @@ import qbs +import qbs.Utilities CppApplication { name: "app" @@ -26,4 +27,13 @@ CppApplication { qbs.install: true qbs.installDir: "data" } + + Probe { + id: qtVersionProbe + property string qtVersion: Qt.core.version + configure: { + console.info("qmlcachegen must work: " + + (Utilities.versionCompare(qtVersion, "5.11") >= 0)) + } + } } diff --git a/tests/auto/blackbox/testdata/changed-rule-inputs/changed-rule-inputs.qbs b/tests/auto/blackbox/testdata/changed-rule-inputs/changed-rule-inputs.qbs new file mode 100644 index 000000000..8aef7b9b6 --- /dev/null +++ b/tests/auto/blackbox/testdata/changed-rule-inputs/changed-rule-inputs.qbs @@ -0,0 +1,42 @@ +import qbs + +Project { + Product { + name: "p1" + type: "p1" + Rule { + alwaysRun: true + multiplex: true + Artifact { + fileTags: "p1" + filePath: "p1-dummy" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() {}; + return cmd; + } + } + } + Product { + name: "p2" + type: "p2" + Depends { name: "p1" } + Rule { + requiresInputs: false + multiplex: true + inputsFromDependencies: "p1" + Artifact { + fileTags: "p2" + filePath: "p2-dummy" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() {}; + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib.pc b/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib.pc new file mode 100644 index 000000000..6a83a6b37 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib.pc @@ -0,0 +1,10 @@ +prefix=/opt/the firstlib + +Name: TheFirstLib +Description: TheFirstLib +Version: 1.0 +URL: http://www.example.com/thefirstlib +Cflags: -DTheFirstLib -I"${prefix}/include" -pthread -DHAVE_INDUSTRIAL_STRENGTH_HAIR_DRYER -I/otherdir/include1 -I/otherdir/include2 +Libs: -L"${prefix}/lib" -lTheFirstLib -pthread +Requires: Qt5Core +Requires.private: SomeHelper diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib_windows.pc b/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib_windows.pc new file mode 100644 index 000000000..3c730a0ad --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib_windows.pc @@ -0,0 +1,10 @@ +prefix=/opt/the firstlib + +Name: TheFirstLib +Description: TheFirstLib +Version: 1.0 +URL: http://www.example.com/thefirstlib +Cflags: -DTheFirstLib -I"${prefix}/include" -DHAVE_INDUSTRIAL_STRENGTH_HAIR_DRYER -I/otherdir/include1 -I/otherdir/include2 +Libs: -L"${prefix}/lib" -lTheFirstLib +Requires: Qt5Core +Requires.private: SomeHelper diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/TheSecondLib.pc b/tests/auto/blackbox/testdata/exports-pkgconfig/TheSecondLib.pc new file mode 100644 index 000000000..6c54b451e --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/TheSecondLib.pc @@ -0,0 +1,9 @@ +config1=a b +config2=c + +Name: TheSecondLib +Description: The second lib +Version: 2.0 +Cflags: -I/opt/thesecondlib/include +Libs: -L/opt/thesecondlib/lib -lTheSecondLib +Requires: TheFirstLib diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/boringstaticlib.cpp b/tests/auto/blackbox/testdata/exports-pkgconfig/boringstaticlib.cpp new file mode 100644 index 000000000..559a0ecfa --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/boringstaticlib.cpp @@ -0,0 +1 @@ +int calculateLuckyNumber() { return 12 * 13; } diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/exports-pkgconfig.qbs b/tests/auto/blackbox/testdata/exports-pkgconfig/exports-pkgconfig.qbs new file mode 100644 index 000000000..674f78c94 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/exports-pkgconfig.qbs @@ -0,0 +1,124 @@ +import qbs +import qbs.FileInfo + +Project { + Product { + name: "dummy" + Export { Depends { name: "TheFirstLib" } } + } + + Product { + name: "SomeHelper" + Depends { name: "Exporter.pkgconfig" } + Exporter.pkgconfig.versionEntry: "1.0" + } + StaticLibrary { + Depends { name: "cpp" } + name: "BoringStaticLib" + files: ["boringstaticlib.cpp"] + Export { + Depends { name: "cpp" } + cpp.defines: ["HAVE_INDUSTRIAL_STRENGTH_HAIR_DRYER"] + } + } + DynamicLibrary { + name: "TheFirstLib" + version: "1.0" + + Depends { name: "SomeHelper" } + Depends { name: "Exporter.pkgconfig" } + Exporter.pkgconfig.excludedDependencies: ["Qt.core", "helper3"] + Exporter.pkgconfig.requiresEntry: "Qt5Core" + Exporter.pkgconfig.urlEntry: "http://www.example.com/thefirstlib" + + Depends { name: "cpp" } + cpp.defines: ["FIRSTLIB"] + + qbs.installPrefix: "/opt/the firstlib" + + Export { + prefixMapping: [{prefix: "/somedir", replacement: "/otherdir"}] + Depends { name: "BoringStaticLib" } + Depends { name: "cpp" } + Depends { name: "Qt.core"; required: false } + Depends { name: "helper1" } + Depends { name: "helper3" } + property bool someCondition: qbs.hostOS.contains("windows") // hostOS for easier testing + property bool someOtherCondition: someCondition + Properties { + condition: !someOtherCondition + cpp.driverFlags: ["-pthread"] + } + cpp.defines: product.name + cpp.includePaths: [FileInfo.joinPaths(product.qbs.installPrefix, "include")] + Qt.core.mocName: "muck" + } + + Group { + fileTagsFilter: ["dynamiclibrary", "dynamiclibrary_import"] + qbs.install: true + qbs.installDir: "lib" + } + + Group { + name: "api_headers" + files: ["firstlib.h"] + qbs.install: true + qbs.installDir: "include" + } + + files: ["firstlib.cpp"] + } + DynamicLibrary { + name: "TheSecondLib" + version: "2.0" + + Depends { name: "Exporter.pkgconfig" } + Exporter.pkgconfig.descriptionEntry: "The second lib" + Exporter.pkgconfig.transformFunction: (function(product, moduleName, propertyName, value) { + if (moduleName === "cpp" && propertyName === "includePaths") + return value.filter(function(p) { return p !== product.sourceDirectory; }); + return value; + }) + Exporter.pkgconfig.customVariables: ({config1: "a b", config2: "c"}) + + Depends { name: "cpp" } + cpp.defines: ["SECONDLIB"] + + qbs.installPrefix: "" + + Depends { name: "TheFirstLib" } + + Export { + Depends { name: "TheFirstLib" } + Depends { name: "dummy" } + Depends { name: "cpp" } + cpp.includePaths: ["/opt/thesecondlib/include", product.sourceDirectory] + property string hurz: importingProduct.name + + Rule { + property int n: 5 + Artifact { + filePath: "dummy" + fileTags: ["d1", "d2"] + cpp.warningsAreErrors: true + } + } + } + + Group { + fileTagsFilter: ["dynamiclibrary", "dynamiclibrary_import"] + qbs.install: true + qbs.installDir: "/opt/thesecondlib/lib" + } + + Group { + name: "api_headers" + files: ["secondlib.h"] + qbs.install: true + qbs.installDir: "/opt/thesecondlib/include" + } + + files: ["secondlib.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.cpp b/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.cpp new file mode 100644 index 000000000..ab48afc94 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.cpp @@ -0,0 +1,3 @@ +#include "firstlib.h" + +void firstLib() { } diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.h b/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.h new file mode 100644 index 000000000..1ccfb0868 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.h @@ -0,0 +1,9 @@ +#include "../dllexport.h" + +#ifdef FIRSTLIB +# define FIRSTLIB_EXPORT DLL_EXPORT +#else +# define FIRSTLIB_EXPORT DLL_IMPORT +#endif + +FIRSTLIB_EXPORT void firstLib(); diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper1/helper1.qbs b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper1/helper1.qbs new file mode 100644 index 000000000..b753ec295 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper1/helper1.qbs @@ -0,0 +1,7 @@ +import qbs + +Module { + Depends { name: "cpp" } + Depends { name: "helper2" } + cpp.includePaths: "/somedir/include1" +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper2/helper2.qbs b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper2/helper2.qbs new file mode 100644 index 000000000..9d24a812d --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper2/helper2.qbs @@ -0,0 +1,6 @@ +import qbs + +Module { + Depends { name: "cpp" } + cpp.includePaths: "/somedir/include2" +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper3/helper3.qbs b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper3/helper3.qbs new file mode 100644 index 000000000..452191994 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper3/helper3.qbs @@ -0,0 +1,6 @@ +import qbs + +Module { + Depends { name: "cpp" } + cpp.includePaths: "/somedir/include3" +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.cpp b/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.cpp new file mode 100644 index 000000000..e1782f576 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.cpp @@ -0,0 +1,7 @@ +#include "secondlib.h" + +#ifndef HAVE_INDUSTRIAL_STRENGTH_HAIR_DRYER +# error I CANT LIVE WITHOUT IT! +#endif + +void secondLib() { } diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.h b/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.h new file mode 100644 index 000000000..78b52f7fe --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.h @@ -0,0 +1,9 @@ +#include "../dllexport.h" + +#ifdef SECONDLIB +# define SECONDLIB_EXPORT DLL_EXPORT +#else +# define SECONDLIB_EXPORT DLL_IMPORT +#endif + +SECONDLIB_EXPORT void secondLib(); diff --git a/tests/auto/blackbox/testdata/import-assignment/import-assignment.qbs b/tests/auto/blackbox/testdata/import-assignment/import-assignment.qbs new file mode 100644 index 000000000..2bff00f35 --- /dev/null +++ b/tests/auto/blackbox/testdata/import-assignment/import-assignment.qbs @@ -0,0 +1,23 @@ +import qbs +import MyImport + +Product { + type: "outtype" + property var importValue: MyImport + Rule { + multiplex: true + Artifact { + fileTags: "outtype" + filePath: "dummy" + } + prepare: { + var cmd = new JavaScriptCommand; + cmd.silent = true; + cmd.sourceCode = function() { + console.info("key 1 = " + product.importValue.key1); + console.info("key 2 = " + product.importValue.key2); + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/import-assignment/imports/MyImport/myimport.js b/tests/auto/blackbox/testdata/import-assignment/imports/MyImport/myimport.js new file mode 100644 index 000000000..5befd5151 --- /dev/null +++ b/tests/auto/blackbox/testdata/import-assignment/imports/MyImport/myimport.js @@ -0,0 +1,2 @@ +var key1 = "value1"; +var key2 = "value2"; diff --git a/tests/auto/blackbox/testdata/probes-and-shadow-products/probes-and-shadow-products.qbs b/tests/auto/blackbox/testdata/probes-and-shadow-products/probes-and-shadow-products.qbs new file mode 100644 index 000000000..660c088a0 --- /dev/null +++ b/tests/auto/blackbox/testdata/probes-and-shadow-products/probes-and-shadow-products.qbs @@ -0,0 +1,13 @@ +import qbs + +Product { + name: "p" + multiplexByQbsProperties: "buildVariants" + qbs.buildVariants: ["debug", "release"] + Export { + Probe { + id: dummy + configure: { found = true; } + } + } +} diff --git a/tests/auto/blackbox/testdata/smart-relinking/smart-relinking.qbs b/tests/auto/blackbox/testdata/smart-relinking/smart-relinking.qbs index 049ade899..44d8013b9 100644 --- a/tests/auto/blackbox/testdata/smart-relinking/smart-relinking.qbs +++ b/tests/auto/blackbox/testdata/smart-relinking/smart-relinking.qbs @@ -25,6 +25,14 @@ Project { condition: tcProbe.found name:"app" Depends { name: "lib" } + Depends { name: "staticlib" } files: ["main.cpp"] } + StaticLibrary { + condition: tcProbe.found + name: "staticlib" + Depends { name: "lib" } + Depends { name: "cpp" } + files: "staticlib.cpp" + } } diff --git a/tests/auto/blackbox/testdata/smart-relinking/staticlib.cpp b/tests/auto/blackbox/testdata/smart-relinking/staticlib.cpp new file mode 100644 index 000000000..24fd8c6e9 --- /dev/null +++ b/tests/auto/blackbox/testdata/smart-relinking/staticlib.cpp @@ -0,0 +1 @@ +static void myFunc() {} diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index 41ce32e40..9022bd975 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -716,6 +716,35 @@ void TestBlackbox::changedFiles() QVERIFY2(m_qbsStdout.contains("file1.cpp"), m_qbsStdout.constData()); } +void TestBlackbox::changedRuleInputs() +{ + QDir::setCurrent(testDataDir + "/changed-rule-inputs"); + + // Initial build. + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData()); + + // Re-build: p1 is always regenerated, and p2 has a dependency on it. + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData()); + + // Remove the dependency. p2 gets re-generated one last time, because its set of + // inputs changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("changed-rule-inputs.qbs", "inputsFromDependencies: \"p1\"", + "inputsFromDependencies: \"p3\""); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData()); + + // Now the artifacts are no longer connected, and p2 must not get rebuilt anymore. + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData()); +} + void TestBlackbox::changeInDisabledProduct() { QDir::setCurrent(testDataDir + "/change-in-disabled-product"); @@ -2750,6 +2779,19 @@ void TestBlackbox::probeProperties() QVERIFY2(m_qbsStdout.contains("probe2.filePath=" + dir + "/bin/tool"), m_qbsStdout.constData()); } +void TestBlackbox::probesAndShadowProducts() +{ + QDir::setCurrent(testDataDir + "/probes-and-shadow-products"); + QCOMPARE(runQbs(QStringList("--log-time")), 0); + QVERIFY2(m_qbsStdout.contains("2 probes encountered, 1 configure scripts executed"), + m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("probes-and-shadow-products.qbs"); + QCOMPARE(runQbs(QStringList("--log-time")), 0); + QVERIFY2(m_qbsStdout.contains("2 probes encountered, 0 configure scripts executed"), + m_qbsStdout.constData()); +} + void TestBlackbox::probeInExportedModule() { QDir::setCurrent(testDataDir + "/probe-in-exported-module"); @@ -3330,6 +3372,22 @@ void TestBlackbox::exportToOutsideSearchPath() m_qbsStderr.constData()); } +void TestBlackbox::exportsPkgconfig() +{ + QDir::setCurrent(testDataDir + "/exports-pkgconfig"); + QCOMPARE(runQbs(), 0); + QFile sourcePcFile(HostOsInfo::isWindowsHost() ? "TheFirstLib_windows.pc" : "TheFirstLib.pc"); + QString generatedPcFilePath = relativeProductBuildDir("TheFirstLib") + "/TheFirstLib.pc"; + QFile generatedPcFile(generatedPcFilePath); + QVERIFY2(sourcePcFile.open(QIODevice::ReadOnly), qPrintable(sourcePcFile.errorString())); + QVERIFY2(generatedPcFile.open(QIODevice::ReadOnly), qPrintable(generatedPcFile.errorString())); + QCOMPARE(generatedPcFile.readAll().replace("\r", ""), sourcePcFile.readAll().replace("\r", "")); + sourcePcFile.close(); + generatedPcFile.close(); + TEXT_FILE_COMPARE(relativeProductBuildDir("TheSecondLib") + "/TheSecondLib.pc", + "TheSecondLib.pc"); +} + void TestBlackbox::exportsQbs() { QDir::setCurrent(testDataDir + "/exports-qbs"); @@ -6260,6 +6318,14 @@ void TestBlackbox::ico() } } +void TestBlackbox::importAssignment() +{ + QDir::setCurrent(testDataDir + "/import-assignment"); + QCOMPARE(runQbs(QStringList("project.qbsSearchPaths:" + QDir::currentPath())), 0); + QVERIFY2(m_qbsStdout.contains("key 1 = value1") && m_qbsStdout.contains("key 2 = value2"), + m_qbsStdout.constData()); +} + void TestBlackbox::importChangeTracking() { QDir::setCurrent(testDataDir + "/import-change-tracking"); diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index 3784a03b1..a2c021140 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -59,6 +59,7 @@ private slots: void buildGraphVersions(); void changedFiles_data(); void changedFiles(); + void changedRuleInputs(); void changeInDisabledProduct(); void changeInImportedFile(); void changeTrackingAndMultiplexing(); @@ -108,6 +109,7 @@ private slots: void exportedPropertyInDisabledProduct_data(); void exportRule(); void exportToOutsideSearchPath(); + void exportsPkgconfig(); void exportsQbs(); void externalLibs(); void fileDependencies(); @@ -117,6 +119,7 @@ private slots: void generator_data(); void groupsInModules(); void ico(); + void importAssignment(); void importChangeTracking(); void importInPropertiesCondition(); void importSearchPath(); @@ -198,6 +201,7 @@ private slots: void preventFloatingPointValues(); void probeChangeTracking(); void probeProperties(); + void probesAndShadowProducts(); void probeInExportedModule(); void probesAndArrayProperties(); void probesInNestedModules(); diff --git a/tests/auto/blackbox/tst_blackboxqt.cpp b/tests/auto/blackbox/tst_blackboxqt.cpp index 93eb68f80..03e52590b 100644 --- a/tests/auto/blackbox/tst_blackboxqt.cpp +++ b/tests/auto/blackbox/tst_blackboxqt.cpp @@ -77,7 +77,11 @@ void TestBlackboxQt::cachedQml() QDir::setCurrent(testDataDir + "/cached-qml"); QCOMPARE(runQbs(), 0); QString dataDir = relativeBuildDir() + "/install-root/data"; - if (QFile::exists(dataDir + "/main.cpp")) { + QVERIFY2(m_qbsStdout.contains("qmlcachegen must work: true") + || m_qbsStdout.contains("qmlcachegen must work: false"), + m_qbsStdout.constData()); + if (m_qbsStdout.contains("qmlcachegen must work: false") + && QFile::exists(dataDir + "/main.cpp")) { // If C++ source files were installed then Qt.qmlcache is not available. See project file. QSKIP("No QML cache files generated."); } |