From 75aca0dca12c6c94109e65ee035b6b533b33a3c5 Mon Sep 17 00:00:00 2001 From: Raphael Cotty Date: Wed, 30 Aug 2023 08:50:28 +0200 Subject: Add CMake Exporter module The module allows to generate a cmake package for Qbs libraries. Change-Id: I4405899ad73e87aeb63bd8471596e89770591b38 Reviewed-by: Christian Kandeler --- doc/config/macros.qdocconf | 2 + doc/reference/modules/exporter-cmake.qdoc | 135 ++++++++++++ examples/exporters/lib_a/lib_a.qbs | 5 + examples/exporters/qbs/imports/MyLibrary.qbs | 7 + share/qbs/modules/Exporter/cmake/cmakeexporter.js | 239 +++++++++++++++++++++ share/qbs/modules/Exporter/cmake/cmakeexporter.qbs | 84 ++++++++ tests/auto/blackbox/testdata/exports-cmake/Foo.cpp | 5 + tests/auto/blackbox/testdata/exports-cmake/Foo.h | 16 ++ .../testdata/exports-cmake/cmake/CMakeLists.txt | 7 + .../blackbox/testdata/exports-cmake/cmake/main.cpp | 6 + .../testdata/exports-cmake/exports-cmake.qbs | 70 ++++++ .../blackbox/testdata/exports-cmake/find-cmake.qbs | 46 ++++ tests/auto/blackbox/tst_blackbox.cpp | 80 ++++++- tests/auto/blackbox/tst_blackbox.h | 2 + 14 files changed, 702 insertions(+), 2 deletions(-) create mode 100644 doc/reference/modules/exporter-cmake.qdoc create mode 100644 share/qbs/modules/Exporter/cmake/cmakeexporter.js create mode 100644 share/qbs/modules/Exporter/cmake/cmakeexporter.qbs create mode 100644 tests/auto/blackbox/testdata/exports-cmake/Foo.cpp create mode 100644 tests/auto/blackbox/testdata/exports-cmake/Foo.h create mode 100644 tests/auto/blackbox/testdata/exports-cmake/cmake/CMakeLists.txt create mode 100644 tests/auto/blackbox/testdata/exports-cmake/cmake/main.cpp create mode 100644 tests/auto/blackbox/testdata/exports-cmake/exports-cmake.qbs create mode 100644 tests/auto/blackbox/testdata/exports-cmake/find-cmake.qbs diff --git a/doc/config/macros.qdocconf b/doc/config/macros.qdocconf index 6811215c0..31b9b9a8d 100644 --- a/doc/config/macros.qdocconf +++ b/doc/config/macros.qdocconf @@ -51,6 +51,8 @@ macro.endfloat.HTML = "" macro.clearfloat.HTML = "
" macro.emptyspan.HTML = "" +macro.CMAKE = "CMake" + # Embed YouTube content by video ID - Example: \youtube dQw4w9WgXcQ # Also requires a .jpg thumbnail for offline docs. In .qdocconf, add: # diff --git a/doc/reference/modules/exporter-cmake.qdoc b/doc/reference/modules/exporter-cmake.qdoc new file mode 100644 index 000000000..2f4191a76 --- /dev/null +++ b/doc/reference/modules/exporter-cmake.qdoc @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2024 Raphael Cotty (raphael.cotty@gmail.com) +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \qmltype Exporter.cmake + \inqmlmodule QbsModules + \since Qbs 2.3 + + \brief Provides support for generating \CMAKE packages from dynamic, static and header library + products. + + The Exporter.cmake module contains the properties and rules to create a \CMAKE config + \l{https://cmake.org/cmake/help/v3.29/manual/cmake-packages.7.html#config-file-packages}{files} + from the \l Export item of a \l Product. + + For instance, suppose you are creating a library. To allow exporting to \CMAKE, you would write + something like the following: + \code + DynamicLibrary { + name: "mylibrary" + qbs.installPrefix: "/opt/mylibrary" + Depends { name: "Exporter.cmake" } + Exporter.cmake.packageName: "MyLibrary" + property string headersInstallDir: "include" + // ... + Group { + name: "API headers" + files: ["mylib.h"] + qbs.install: true + qbs.installDir: headersInstallDir + } + Group { + fileTagsFilter: ["Exporter.cmake.package"] + qbs.installDir: "lib/cmake/MyLibrary" + } + Export { + Depends { name: "cpp" } + cpp.includePaths: FileInfo.joinPaths( + exportingProduct.qbs.installRoot, + exportingProduct.qbs.installPrefix, + exportingProduct.headersInstallDir) + } + } + \endcode + To build against this library, from within your \CMAKE project, you simply + use \l{https://cmake.org/cmake/help/v3.29/command/find_package.html}{find_package}: + \code + find_package(MyLibrary PATHS REQUIRED) + add_executable(Consumer main.cpp) + target_link_libraries(Consumer MyLibrary) + \endcode + + \section2 Relevant File Tags + \target filetags-exporter-qbs + + \table + \header + \li Tag + \li Since + \li Description + \row + \li \c{"Exporter.cmake.package"} + \li 2.3.0 + \li This tag is attached to all generated module files. + \row + \li \c{"Exporter.cmake.configFile"} + \li 2.3.0 + \li This tag is attached to the generated config file. + \row + \li \c{"Exporter.cmake.versionFile"} + \li 2.3.0 + \li This tag is attached to the generated version file. + \endtable +*/ + +/*! + \qmlproperty string Exporter.cmake::configFileName + + The name of the generated config file. + + \defaultvalue \c{packageName + "Config.cmake"} +*/ + +/*! + \qmlproperty string Exporter.cmake::versionFileName + + The name of the generated version file. + + \defaultvalue \c{packageName + "ConfigVersion.cmake"} +*/ + +/*! + \qmlproperty string Exporter.cmake::packageName + + The name of the \CMAKE package. + + \defaultvalue \l{Product::targetName}{Product.targetName} +*/ + diff --git a/examples/exporters/lib_a/lib_a.qbs b/examples/exporters/lib_a/lib_a.qbs index f058318e4..fedbb7074 100644 --- a/examples/exporters/lib_a/lib_a.qbs +++ b/examples/exporters/lib_a/lib_a.qbs @@ -58,5 +58,10 @@ MyLibrary { qbs.install: true qbs.installDir: headersInstallDir } + Export { + Depends { name: "cpp" } + cpp.dynamicLibraries: "z" + cpp.libraryPaths: "/opt/local/lib" + } } diff --git a/examples/exporters/qbs/imports/MyLibrary.qbs b/examples/exporters/qbs/imports/MyLibrary.qbs index 3dcfb2796..4607776b3 100644 --- a/examples/exporters/qbs/imports/MyLibrary.qbs +++ b/examples/exporters/qbs/imports/MyLibrary.qbs @@ -83,6 +83,13 @@ StaticLibrary { qbs.installDir: FileInfo.joinPaths(installDir, "pkgconfig") } + Depends { name: "Exporter.cmake" } + Group { + fileTagsFilter: ["Exporter.cmake.package"] + qbs.install: install + qbs.installDir: FileInfo.joinPaths(installDir, "cmake", product.name) + } + Depends { name: 'bundle' } bundle.isBundle: false } diff --git a/share/qbs/modules/Exporter/cmake/cmakeexporter.js b/share/qbs/modules/Exporter/cmake/cmakeexporter.js new file mode 100644 index 000000000..093032f2e --- /dev/null +++ b/share/qbs/modules/Exporter/cmake/cmakeexporter.js @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** Copyright (C) 2024 Raphaël Cotty +** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com) +** Contact: http://www.qt.io/licensing +** +** This file is part of the Qbs. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); +var ExporterHelpers = require("../exporter.js"); + +function tagListToString(tagList) +{ + return JSON.stringify(tagList); +} + +function collectAutodetectedData(project, topLevelProduct, outputs) +{ + var packageName = topLevelProduct.Exporter.cmake.packageName; + + var data = {}; + data.packageName = packageName; + data.installPrefixDir = "_" + packageName.toUpperCase() + "_INSTALL_PREFIX"; + data.packages = []; + + function quote(value) + { + return "\"" + value + "\""; + } + + function quoteAndPrefixify(propName) + { + function quoteAndPrefixifyHelper(value) { + var prefixToStrip = + ExporterHelpers.getPrefixToStrip(project, topLevelProduct, propName, value); + if (typeof value !== "string" + || !prefixToStrip + || (value.length > prefixToStrip.length + && value[prefixToStrip.length] !== '/')) { + return quote(value); + } + return quote("${" + data.installPrefixDir + "}" + value.slice(prefixToStrip.length)); + } + return quoteAndPrefixifyHelper; + } + + var installedOutputFilePath = ModUtils.artifactInstalledFilePath( + outputs["Exporter.cmake.configFile"][0]); + var installedOutputPathName = FileInfo.path(installedOutputFilePath); + + var installRootPath = FileInfo.joinPaths(topLevelProduct.qbs.installRoot, topLevelProduct.qbs.installPrefix); + data.installPrefix = FileInfo.relativePath(installedOutputPathName, installRootPath); + + var libArtifacts; + var libImportArtifacts; + var isProduct = !topLevelProduct.present; + var considerFramework = !isProduct || (topLevelProduct.type + && topLevelProduct.type.includes("bundle.content")) + && topLevelProduct.bundle + && topLevelProduct.bundle.isBundle + && topLevelProduct.qbs.targetOS.includes("darwin"); + var considerDynamicLibs = !isProduct || (topLevelProduct.type + && topLevelProduct.type.includes("dynamiclibrary")); + var considerStaticLibs = !isProduct || (topLevelProduct.type + && topLevelProduct.type.includes("staticlibrary")); + if (considerFramework) { + libArtifacts = topLevelProduct.artifacts["bundle.symlink.executable"]; + if (considerDynamicLibs) + data.type = "SHARED"; + else if (considerStaticLibs) + data.type = "STATIC"; + else + data.type = "INTERFACE"; + } else if (considerDynamicLibs) { + libArtifacts = topLevelProduct.artifacts.dynamiclibrary; + libImportArtifacts = topLevelProduct.artifacts.dynamiclibrary_import; + data.type = "SHARED"; + } else if (considerStaticLibs) { + libArtifacts = topLevelProduct.artifacts.staticlibrary; + data.type = "STATIC"; + } else { + data.type = "INTERFACE"; + } + + for (var i = 0; i < (libArtifacts || []).length; ++i) { + var libArtifact = libArtifacts[i]; + var libImportArtifact = (libImportArtifacts || [])[i]; + if (libArtifact.qbs.install) { + var installPath = ModUtils.artifactInstalledFilePath(libArtifact); + data.importedLocation = quoteAndPrefixify("installRoot")(installPath); + data.soName = topLevelProduct.targetName; + if (libImportArtifact && libImportArtifact.qbs.install) { + installPath = ModUtils.artifactInstalledFilePath(libImportArtifact); + data.importedImplibLocation = quoteAndPrefixify("installRoot")(installPath); + } + break; + } + } + var cpp = topLevelProduct.exports.cpp; + if (cpp) { + data.libraryPaths = (cpp.libraryPaths || []).map(quoteAndPrefixify("cpp.libraryPaths")); + + data.linkLibraries = []; + data.linkLibraries = data.linkLibraries.concat(cpp.dynamicLibraries || []); + data.linkLibraries = data.linkLibraries.concat(cpp.staticLibraries || []); + data.linkLibraries = data.linkLibraries.map(quoteAndPrefixify("cpp.dynamicLibraries")); + + data.linkOptions = []; + data.linkOptions = data.linkOptions.concat(cpp.driverLinkerFlags || []); + if ((cpp.linkerFlags || []).length > 0) { + data.linkOptions = + data.linkOptions.concat("LINKER:" + (cpp.linkerFlags || []).join(",")); + } + data.linkOptions = data.linkOptions.map(quote); + + data.includeDirectories = + (cpp.includePaths || []).map(quoteAndPrefixify("cpp.includePaths")); + data.compileDefinitions = (cpp.defines || []).map(quote); + + data.compileOptions = []; + data.compileOptions = data.compileOptions.concat(cpp.commonCompilerFlags || []); + data.compileOptions = data.compileOptions.concat(cpp.driverFlags || []); + data.compileOptions = data.compileOptions.concat(cpp.cxxFlags || []); + data.compileOptions = data.compileOptions.concat(cpp.cFlags || []); + data.compileOptions = data.compileOptions.map(quote); + } + + function gatherDeps(dep) { + if (dep.name === "Exporter.cmake") + return; + var depHasExporter = dep.Exporter && dep.Exporter.cmake; + if (!depHasExporter) + return; + data.packages.push(dep.Exporter.cmake.packageName); + } + + var exportedDeps = topLevelProduct.exports ? topLevelProduct.exports.dependencies : []; + exportedDeps.forEach(gatherDeps); + + return data; +} + +function writeConfigFile(project, product, outputs) +{ + var autoDetectedData = collectAutodetectedData(project, product, outputs); + var packageName = autoDetectedData.packageName; + + function writeCommand(command, lines) + { + if ((lines || []).length === 0) + return; + cmakeConfigFile.writeLine(command + "(" + packageName + " INTERFACE"); + for (i = 0; i < lines.length; i++) { + cmakeConfigFile.writeLine(" " + lines[i]); + } + cmakeConfigFile.writeLine(")"); + } + + var cmakeConfigFile = new TextFile(outputs["Exporter.cmake.configFile"][0].filePath, + TextFile.WriteOnly); + cmakeConfigFile.writeLine("# Generated by Qbs"); + + cmakeConfigFile.writeLine("cmake_minimum_required(VERSION 3.5)"); + + cmakeConfigFile.writeLine("if(TARGET " + packageName + ")"); + cmakeConfigFile.writeLine(" return()"); + cmakeConfigFile.writeLine("endif()"); + + cmakeConfigFile.writeLine("set(" + autoDetectedData.installPrefixDir + + " \"${CMAKE_CURRENT_LIST_DIR}/" + + autoDetectedData.installPrefix + "\")"); + + autoDetectedData.packages.forEach(function(packageName) { + cmakeConfigFile.writeLine("find_package(" + packageName + " REQUIRED SILENT)"); + }); + cmakeConfigFile.writeLine( + "add_library(" + packageName + " " + autoDetectedData.type + " IMPORTED)"); + var configuration = (product.qbs.buildVariant) ? + product.qbs.buildVariant.toUpperCase() : "NONE"; + cmakeConfigFile.writeLine("set_property(TARGET " + packageName + + " APPEND PROPERTY IMPORTED_CONFIGURATIONS " + + configuration + ")"); + + cmakeConfigFile.writeLine("set_target_properties(" + packageName + " PROPERTIES"); + cmakeConfigFile.writeLine(" IMPORTED_LINK_INTERFACE_LANGUAGES_" + configuration + + " CXX"); + if (autoDetectedData.type !== "INTERFACE") { + cmakeConfigFile.writeLine(" IMPORTED_LOCATION_" + configuration + " " + + autoDetectedData.importedLocation); + } + if (autoDetectedData.importedImplibLocation) { + cmakeConfigFile.writeLine(" IMPORTED_IMPLIB_" + configuration + " " + + autoDetectedData.importedImplibLocation); + } + cmakeConfigFile.writeLine(")"); + + writeCommand("target_link_directories", autoDetectedData.libraryPaths); + writeCommand("target_link_libraries", + autoDetectedData.linkLibraries.concat(autoDetectedData.packages)); + writeCommand("target_link_options", autoDetectedData.linkOptions); + writeCommand("target_include_directories", autoDetectedData.includeDirectories); + writeCommand("target_compile_definitions", autoDetectedData.compileDefinitions); + writeCommand("target_compile_options", autoDetectedData.compileOptions); + + cmakeConfigFile.close(); +} + +function writeVersionFile(product, outputs) +{ + var cmakeVersionFile = new TextFile( + outputs["Exporter.cmake.versionFile"][0].filePath, TextFile.WriteOnly); + cmakeVersionFile.writeLine("# Generated by Qbs"); + cmakeVersionFile.writeLine("set(PACKAGE_VERSION \"" + product.version + "\")"); + cmakeVersionFile.close(); +} diff --git a/share/qbs/modules/Exporter/cmake/cmakeexporter.qbs b/share/qbs/modules/Exporter/cmake/cmakeexporter.qbs new file mode 100644 index 000000000..a578e938b --- /dev/null +++ b/share/qbs/modules/Exporter/cmake/cmakeexporter.qbs @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2024 Raphaël Cotty +** Copyright (C) 2024 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.TextFile + +import "cmakeexporter.js" as HelperFunctions + +Module { + property string configFileName: packageName + "Config.cmake" + property string versionFileName: packageName + "ConfigVersion.cmake" + property string packageName: product.targetName + + additionalProductTypes: ["Exporter.cmake.package"] + + Rule { + multiplex: true + requiresInputs: false + + auxiliaryInputs: { + if (product.type.includes("staticlibrary")) + return ["staticlibrary"]; + if (product.type.includes("dynamiclibrary")) + return ["dynamiclibrary"]; + } + + Artifact { + filePath: product.Exporter.cmake.configFileName + fileTags: ["Exporter.cmake.package", "Exporter.cmake.configFile"] + } + Artifact { + filePath: product.Exporter.cmake.versionFileName + fileTags: ["Exporter.cmake.package", "Exporter.cmake.versionFile"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generate cmake package files"; + cmd.sourceCode = function() { + HelperFunctions.writeConfigFile(project, product, outputs); + HelperFunctions.writeVersionFile(product, outputs); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/exports-cmake/Foo.cpp b/tests/auto/blackbox/testdata/exports-cmake/Foo.cpp new file mode 100644 index 000000000..ea334f9af --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-cmake/Foo.cpp @@ -0,0 +1,5 @@ +#include "Foo.h" +int someFooWork() +{ + return 42; +} diff --git a/tests/auto/blackbox/testdata/exports-cmake/Foo.h b/tests/auto/blackbox/testdata/exports-cmake/Foo.h new file mode 100644 index 000000000..2f279f577 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-cmake/Foo.h @@ -0,0 +1,16 @@ +#ifndef FOO_H +#define FOO_H +#include + +#ifdef FOO_LIB_STATIC +#define FOO_LIB_EXPORT +#else +#ifdef FOO_LIB +#define FOO_LIB_EXPORT DLL_EXPORT +#else +#define FOO_LIB_EXPORT DLL_IMPORT +#endif +#endif + +FOO_LIB_EXPORT int someFooWork(); +#endif // FOO_H diff --git a/tests/auto/blackbox/testdata/exports-cmake/cmake/CMakeLists.txt b/tests/auto/blackbox/testdata/exports-cmake/cmake/CMakeLists.txt new file mode 100644 index 000000000..d874e0e92 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-cmake/cmake/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.10) + +project(qbs_import) + +find_package(Bar PATHS REQUIRED) +add_executable(Consumer main.cpp) +target_link_libraries(Consumer Bar) diff --git a/tests/auto/blackbox/testdata/exports-cmake/cmake/main.cpp b/tests/auto/blackbox/testdata/exports-cmake/cmake/main.cpp new file mode 100644 index 000000000..1a1fa90ec --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-cmake/cmake/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() +{ + return someFooWork(); +} diff --git a/tests/auto/blackbox/testdata/exports-cmake/exports-cmake.qbs b/tests/auto/blackbox/testdata/exports-cmake/exports-cmake.qbs new file mode 100644 index 000000000..6464af705 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-cmake/exports-cmake.qbs @@ -0,0 +1,70 @@ +import qbs.FileInfo + +Project { + property bool isStatic: false + property bool isBundle: false + + property string headersInstallDir: "include" + + Product { + name: "DllExport" + Depends { name: "Exporter.cmake" } + Group { + name: "API headers" + files: ["../dllexport.h"] + qbs.install: true + qbs.installDir: project.headersInstallDir + } + Group { + fileTagsFilter: ["Exporter.cmake.package"] + qbs.install: true + qbs.installDir: "/lib/cmake/DllExport" + } + Export { + Depends { name: "cpp" } + cpp.includePaths: FileInfo.joinPaths( + exportingProduct.qbs.installRoot, + exportingProduct.qbs.installPrefix, + project.headersInstallDir) + } + } + + Library { + type: project.isStatic ? "staticlibrary" : "dynamiclibrary" + Depends { name: "cpp" } + Depends { name: "DllExport" } + Depends { name: "Exporter.cmake" } + Exporter.cmake.packageName: "Bar" + name: "Foo" + files: ["Foo.cpp"] + version: "1.2.3" + cpp.includePaths: "." + cpp.defines: "FOO_LIB" + Group { + name: "API headers" + files: ["Foo.h"] + qbs.install: true + qbs.installDir: project.headersInstallDir + } + install: true + installImportLib: true + Group { + fileTagsFilter: ["Exporter.cmake.package"] + qbs.install: true + qbs.installDir: "/lib/cmake/Bar" + } + Export { + Depends { name: "cpp" } + cpp.includePaths: FileInfo.joinPaths( + exportingProduct.qbs.installRoot, + exportingProduct.qbs.installPrefix, + project.headersInstallDir) + cpp.defines: ["FOO=1"].concat(project.isStatic ? ["FOO_LIB_STATIC"] : []) + cpp.commonCompilerFlags: "-DOTHER_DEF=1" + cpp.linkerFlags: exportingProduct.qbs.toolchain.contains("gcc") ? ["-s"] : [] + } + + Depends { name: 'bundle' } + bundle.isBundle: qbs.targetOS.includes("darwin") && project.isBundle + } +} diff --git a/tests/auto/blackbox/testdata/exports-cmake/find-cmake.qbs b/tests/auto/blackbox/testdata/exports-cmake/find-cmake.qbs new file mode 100644 index 000000000..52f388966 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-cmake/find-cmake.qbs @@ -0,0 +1,46 @@ +import qbs.Probes + +Product { + Depends { name: "cpp" } + + Probes.BinaryProbe { + id: cmakeProbe + names: "cmake" + } + + Probes.BinaryProbe { + id: ninjaProbe + names: ["ninja"] + } + + property bool test: { + var data = { + cmakeFound: cmakeProbe.found, + cmakeFilePath: cmakeProbe.filePath, + crossCompiling: qbs.targetPlatform !== qbs.hostPlatform, + installPrefix: qbs.installPrefix + }; + data.buildEnv = {} + Object.assign(data.buildEnv, cpp.buildEnv); // deep copy buildEnv from a probe + if (qbs.toolchain.includes("gcc")) { + data.buildEnv["CC"] = cpp.cCompilerName; + data.buildEnv["CXX"] = cpp.cxxCompilerName; + } else { + data.buildEnv["CC"] = cpp.compilerName; + data.buildEnv["CXX"] = cpp.compilerName; + } + + if (ninjaProbe.found) { + data.generator = "Ninja"; + } else { + if (qbs.toolchain.includes("msvc")) { + data.generator = "NMake Makefiles" + } else if (qbs.toolchain.includes("mingw")) { + data.generator = "MinGW Makefiles"; + } else if (qbs.toolchain.includes("gcc")) { + data.generator = "Unix Makefiles"; + } + } + console.info("---" + JSON.stringify(data) + "---"); + } +} diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp index 395e5aba6..cd611e76f 100644 --- a/tests/auto/blackbox/tst_blackbox.cpp +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -1386,9 +1386,9 @@ void TestBlackbox::variantSuffix_data() std::make_pair(QString("unix"), QStringList())}); } -static bool waitForProcessSuccess(QProcess &p) +static bool waitForProcessSuccess(QProcess &p, int msecs = 30000) { - if (!p.waitForStarted() || !p.waitForFinished()) { + if (!p.waitForStarted(msecs) || !p.waitForFinished(msecs)) { qDebug() << p.errorString(); return false; } @@ -4099,6 +4099,82 @@ void TestBlackbox::exportToOutsideSearchPath() m_qbsStderr.constData()); } +void TestBlackbox::exportsCMake() +{ + QFETCH(QStringList, arguments); + + QDir::setCurrent(testDataDir + "/exports-cmake"); + rmDirR(relativeBuildDir()); + QbsRunParameters findCMakeParams("resolve", {"-f", "find-cmake.qbs"}); + QCOMPARE(runQbs(findCMakeParams), 0); + const QString output = QString::fromLocal8Bit(m_qbsStdout); + const QRegularExpression pattern( + QRegularExpression::anchoredPattern(".*---(.*)---.*"), + QRegularExpression::DotMatchesEverythingOption); + const QRegularExpressionMatch match = pattern.match(output); + QVERIFY2(match.hasMatch(), qPrintable(output)); + QCOMPARE(pattern.captureCount(), 1); + const QString jsonString = match.captured(1); + const QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8()); + const QJsonObject jsonData = jsonDoc.object(); + + rmDirR(relativeBuildDir()); + const QStringList exporterArgs{"-f", "exports-cmake.qbs"}; + QbsRunParameters exporterRunParams("build", exporterArgs); + exporterRunParams.arguments << arguments; + QCOMPARE(runQbs(exporterRunParams), 0); + + if (!jsonData.value(u"cmakeFound").toBool()) { + QSKIP("cmake is not installed"); + return; + } + + if (jsonData.value(u"crossCompiling").toBool()) { + QSKIP("test is not applicable with cross-compile toolchains"); + return; + } + + const auto cmakeFilePath = jsonData.value(u"cmakeFilePath").toString(); + QVERIFY(!cmakeFilePath.isEmpty()); + + const auto generator = jsonData.value(u"generator").toString(); + if (generator.isEmpty()) { + QSKIP("cannot detect cmake generator"); + return; + } + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + const auto buildEnv = jsonData.value(u"buildEnv").toObject(); + for (auto it = buildEnv.begin(), end = buildEnv.end(); it != end; ++it) { + env.insert(it.key(), it.value().toString()); + } + + const auto installPrefix = jsonData.value(u"installPrefix").toString(); + const auto cmakePrefixPath = QFileInfo(relativeBuildDir()).absoluteFilePath() + "/install-root/" + + installPrefix + "/lib/cmake"; + const auto sourceDirectory = testDataDir + "/exports-cmake/cmake"; + QProcess configure; + configure.setProcessEnvironment(env); + configure.setWorkingDirectory(sourceDirectory); + configure.start( + cmakeFilePath, {".", "-DCMAKE_PREFIX_PATH=" + cmakePrefixPath, "-G" + generator}); + QVERIFY(waitForProcessSuccess(configure, 120000)); + + QProcess build; + build.setProcessEnvironment(env); + build.setWorkingDirectory(sourceDirectory); + build.start(cmakeFilePath, QStringList{"--build", "."}); + QVERIFY(waitForProcessSuccess(build)); +} + +void TestBlackbox::exportsCMake_data() +{ + QTest::addColumn("arguments"); + QTest::newRow("dynamic lib") << QStringList("project.isStatic: false"); + QTest::newRow("static lib") << QStringList("project.isStatic: true"); + QTest::newRow("framework") << QStringList("project.isBundle: true"); +} + void TestBlackbox::exportsPkgconfig() { QDir::setCurrent(testDataDir + "/exports-pkgconfig"); diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h index 62676625e..701002620 100644 --- a/tests/auto/blackbox/tst_blackbox.h +++ b/tests/auto/blackbox/tst_blackbox.h @@ -126,6 +126,8 @@ private slots: void exportedPropertyInDisabledProduct_data(); void exportRule(); void exportToOutsideSearchPath(); + void exportsCMake(); + void exportsCMake_data(); void exportsPkgconfig(); void exportsQbs(); void externalLibs(); -- cgit v1.2.3