diff options
-rw-r--r-- | doc/reference/module-providers/qbspkgconfig-module-provider.qdoc | 12 | ||||
-rw-r--r-- | share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs | 9 | ||||
-rw-r--r-- | share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js | 3 | ||||
-rw-r--r-- | share/qbs/module-providers/qbspkgconfig.qbs | 3 | ||||
-rw-r--r-- | src/lib/corelib/jsextensions/pkgconfigjs.cpp | 1 | ||||
-rw-r--r-- | src/lib/pkgconfig/pcpackage.cpp | 71 | ||||
-rw-r--r-- | src/lib/pkgconfig/pcpackage.h | 22 | ||||
-rw-r--r-- | src/lib/pkgconfig/pcparser.cpp | 69 | ||||
-rw-r--r-- | src/lib/pkgconfig/pcparser.h | 1 | ||||
-rw-r--r-- | src/lib/pkgconfig/pkgconfig.h | 1 | ||||
-rw-r--r-- | tests/auto/pkgconfig/testdata/lib/pkgconfig/prefix.pc | 13 | ||||
-rw-r--r-- | tests/auto/pkgconfig/tst_pkgconfig.cpp | 64 | ||||
-rw-r--r-- | tests/auto/pkgconfig/tst_pkgconfig.h | 4 |
13 files changed, 239 insertions, 34 deletions
diff --git a/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc b/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc index 461536fbc..51f3bccb7 100644 --- a/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc +++ b/doc/reference/module-providers/qbspkgconfig-module-provider.qdoc @@ -87,6 +87,18 @@ */ /*! + \qmlproperty bool qbspkgconfig::definePrefix + + If this property is \c true, then \QBS will override the ${prefix} variable in the packages + with a value that is guessed based on the location of the .pc file. + + This option corresponds to the \c --define-prefix / \c --dont-define-prefix command line + options of the \c pkg-config tool. + + \defaultvalue \c true on Windows, \c false otherwise +*/ + +/*! \qmlproperty path qbspkgconfig::sysroot Set this property if you need to overwrite the default search sysroot path used by diff --git a/share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs b/share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs index 47f162955..56b04227e 100644 --- a/share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs +++ b/share/qbs/imports/qbs/Probes/QbsPkgConfigProbe.qbs @@ -37,6 +37,7 @@ Probe { property stringList _extraPaths property stringList _libDirs property bool _staticMode: false + property bool _definePrefix: false property path _sysroot @@ -51,7 +52,13 @@ Probe { configure: { var result = PkgConfigProbeConfigure.configure( - _executableFilePath, _extraPaths, _libDirs, _staticMode, _sysroot, _mergeDependencies); + _executableFilePath, + _extraPaths, + _libDirs, + _staticMode, + _definePrefix, + _sysroot, + _mergeDependencies); packages = result.packages; packagesByModuleName = result.packagesByModuleName; brokenPackages = result.brokenPackages; diff --git a/share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js b/share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js index 14cb3f81a..ed75a027c 100644 --- a/share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js +++ b/share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js @@ -81,7 +81,7 @@ function configureQt(pkg) { } function configure( - executableFilePath, extraPaths, libDirs, staticMode, sysroot, mergeDependencies) { + executableFilePath, extraPaths, libDirs, staticMode, definePrefix, sysroot, mergeDependencies) { var result = {}; result.packages = []; @@ -92,6 +92,7 @@ function configure( var options = {}; options.libDirs = libDirs; options.sysroot = sysroot; + options.definePrefix = definePrefix; if (options.sysroot) options.allowSystemLibraryPaths = true; options.staticMode = staticMode; diff --git a/share/qbs/module-providers/qbspkgconfig.qbs b/share/qbs/module-providers/qbspkgconfig.qbs index ca04f0613..aebed0ab3 100644 --- a/share/qbs/module-providers/qbspkgconfig.qbs +++ b/share/qbs/module-providers/qbspkgconfig.qbs @@ -40,6 +40,7 @@ import qbs.Environment import qbs.File import qbs.FileInfo +import qbs.Host import qbs.ModUtils import qbs.PkgConfig import qbs.ProviderUtils @@ -54,6 +55,7 @@ ModuleProvider { property stringList extraPaths property stringList libDirs property bool staticMode: false + property bool definePrefix: Host.os().includes("windows") // We take the sysroot default from qbs.sysroot, except for Xcode toolchains, where // the sysroot points into the Xcode installation and does not contain .pc files. @@ -74,6 +76,7 @@ ModuleProvider { _sysroot: parent.sysroot _libDirs: parent.libDirs _staticMode: parent.staticMode + _definePrefix: parent.definePrefix } isEager: false diff --git a/src/lib/corelib/jsextensions/pkgconfigjs.cpp b/src/lib/corelib/jsextensions/pkgconfigjs.cpp index d84c42d8e..e663c537f 100644 --- a/src/lib/corelib/jsextensions/pkgconfigjs.cpp +++ b/src/lib/corelib/jsextensions/pkgconfigjs.cpp @@ -241,6 +241,7 @@ PkgConfig::Options PkgConfigJs::convertOptions(const QProcessEnvironment &env, c [](const QString &str){ return str.toStdString(); }); result.disableUninstalled = map.value(QStringLiteral("disableUninstalled"), true).toBool(); result.staticMode = map.value(QStringLiteral("staticMode"), false).toBool(); + result.definePrefix = map.value(QStringLiteral("definePrefix"), false).toBool(); result.mergeDependencies = map.value(QStringLiteral("mergeDependencies"), true).toBool(); result.globalVariables = variablesFromQVariantMap(map.value(QStringLiteral("globalVariables")).toMap()); diff --git a/src/lib/pkgconfig/pcpackage.cpp b/src/lib/pkgconfig/pcpackage.cpp index 984936bd1..9aaefcfde 100644 --- a/src/lib/pkgconfig/pcpackage.cpp +++ b/src/lib/pkgconfig/pcpackage.cpp @@ -43,6 +43,8 @@ namespace qbs { +using Internal::startsWith; + using ComparisonType = PcPackage::RequiredVersion::ComparisonType; std::string_view PcPackage::Flag::typeToString(Type t) @@ -123,6 +125,26 @@ std::optional<ComparisonType> PcPackage::RequiredVersion::comparisonFromString(s return std::nullopt; } +// see https://github.com/pkgconf/pkgconf/blob/pkgconf-2.1.0/libpkgconf/tuple.c#L194 +bool PcPackage::shouldRewriteSysroot(std::string_view sysroot, std::string_view value) +{ + if (sysroot.empty() || value.empty()) + return false; + + if (value.front() != '/') + return false; + + if (sysroot == "/") + return false; + + if (startsWith(value, sysroot)) + return false; + + return true; +} + +// TODO: pkg-config only prepends sysroot to flags; while pkgconf does this as a part of the +// variable substitution and thus this affects all variables, not only flags PcPackage PcPackage::prependSysroot(std::string_view sysroot) && { PcPackage package(std::move(*this)); @@ -132,6 +154,8 @@ PcPackage PcPackage::prependSysroot(std::string_view sysroot) && if (sysroot.empty()) return flags; for (auto &flag : flags) { + if (!shouldRewriteSysroot(sysroot, flag.value)) + continue; if (flag.type == Flag::Type::IncludePath || flag.type == Flag::Type::SystemIncludePath || flag.type == Flag::Type::DirAfterIncludePath @@ -169,4 +193,51 @@ PcPackage PcPackage::removeSystemLibraryPaths( return package; } +namespace Internal { + +std::string_view fileName(std::string_view filePath) +{ + const auto pos = filePath.rfind('/'); + if (pos == std::string_view::npos) { +#if defined(WIN32) + if (filePath.size() >= 2 && filePath[1] == ':') + return filePath.substr(2); +#endif + return filePath; + } + return filePath.substr(pos + 1); +} + +std::string_view completeBaseName(std::string_view filePath) +{ + filePath = fileName(filePath); + const auto pos = filePath.rfind('.'); + return pos == std::string_view::npos + ? filePath + : filePath.substr(0, pos); +} + +std::string_view parentPath(std::string_view path) +{ + if (path.empty()) + return {}; + auto pos = path.rfind('/'); + if (pos == std::string_view::npos) { +#if defined(WIN32) + if (path.size() >= 2 && path[1] == ':') + return path.substr(0, 2); +#endif + return "."; + } + if (pos == 0) + return "/"; +#if defined(WIN32) + if (pos == 2 && path[1] == ':') + return path.substr(0, pos + 1); +#endif + return path.substr(0, pos); +}; + +} // namespace Internal + } // namespace qbs diff --git a/src/lib/pkgconfig/pcpackage.h b/src/lib/pkgconfig/pcpackage.h index 9d86816aa..794e2fc40 100644 --- a/src/lib/pkgconfig/pcpackage.h +++ b/src/lib/pkgconfig/pcpackage.h @@ -112,12 +112,14 @@ public: std::vector<RequiredVersion> requiresPublic; std::vector<RequiredVersion> requiresPrivate; std::vector<RequiredVersion> conflicts; + std::optional<std::string> oldPrefix; using VariablesMap = std::map<std::string, std::string, std::less<>>; VariablesMap variables; bool uninstalled{false}; + static bool shouldRewriteSysroot(std::string_view sysroot, std::string_view value); PcPackage prependSysroot(std::string_view sysroot) &&; PcPackage removeSystemLibraryPaths(const std::unordered_set<std::string> &libraryPaths) &&; }; @@ -181,6 +183,26 @@ inline bool operator!=(const PcPackage::Flag &lhs, const PcPackage::Flag &rhs) return !(lhs == rhs); } +namespace Internal { + +// fast versions (no allocations) of the QFileInfo methods +std::string_view fileName(std::string_view filePath); +std::string_view completeBaseName(std::string_view filePath); +std::string_view parentPath(std::string_view path); + +inline bool startsWith(std::string_view haystack, std::string_view needle) +{ + return haystack.size() >= needle.size() && haystack.compare(0, needle.size(), needle) == 0; +} + +inline bool endsWith(std::string_view haystack, std::string_view needle) +{ + return haystack.size() >= needle.size() + && haystack.compare(haystack.size() - needle.size(), needle.size(), needle) == 0; +} + +} // Internal + } // namespace qbs namespace std { diff --git a/src/lib/pkgconfig/pcparser.cpp b/src/lib/pkgconfig/pcparser.cpp index 388363af6..4469ca193 100644 --- a/src/lib/pkgconfig/pcparser.cpp +++ b/src/lib/pkgconfig/pcparser.cpp @@ -62,6 +62,11 @@ namespace std { namespace qbs { +using Internal::completeBaseName; +using Internal::parentPath; +using Internal::startsWith; +using Internal::endsWith; + namespace { // workaround for a missing ctor before c++20 @@ -195,17 +200,6 @@ std::optional<std::vector<std::string>> splitCommand(std::string_view s) return result; } -bool startsWith(std::string_view haystack, std::string_view needle) -{ - return haystack.size() >= needle.size() && haystack.compare(0, needle.size(), needle) == 0; -} - -bool endsWith(std::string_view haystack, std::string_view needle) -{ - return haystack.size() >= needle.size() - && haystack.compare(haystack.size() - needle.size(), needle.size(), needle) == 0; -} - [[noreturn]] void raizeUnknownComparisonException(const PcPackage &pkg, std::string_view verName, std::string_view comp) { std::string message; @@ -397,17 +391,6 @@ PcPackage::RequiredVersion::ComparisonType comparisonFromString( raizeUnknownComparisonException(pkg, verName, comp); } -std::string baseName(const std::string_view &filePath) -{ - auto pos = filePath.rfind('/'); - const auto fileName = - pos == std::string_view::npos ? std::string_view() : filePath.substr(pos + 1); - pos = fileName.rfind('.'); - return std::string(pos == std::string_view::npos - ? std::string_view() - : fileName.substr(0, pos)); -} - } // namespace PcParser::PcParser(const PkgConfig &pkgConfig) @@ -429,7 +412,7 @@ try if (!file.is_open()) throw PcException(std::string("Can't open file ") + path); - package.baseFileName = baseName(path); + package.baseFileName = std::string{completeBaseName(path)}; #if HAS_STD_FILESYSTEM const auto fsPath = std::filesystem::path(path); package.filePath = fsPath.generic_string(); @@ -445,7 +428,7 @@ try parseLine(package, line); return package; } catch(const PcException &ex) { - return PcBrokenPackage{path, baseName(path), ex.what()}; + return PcBrokenPackage{path, std::string{completeBaseName(path)}, ex.what()}; } std::string PcParser::trimAndSubstitute(const PcPackage &pkg, std::string_view str) const @@ -488,6 +471,35 @@ std::string PcParser::trimAndSubstitute(const PcPackage &pkg, std::string_view s return result; } +std::string PcParser::evaluateVariable( + PcPackage &pkg, std::string_view tag, std::string_view str) const +{ + static constexpr std::string_view prefixVariable = "prefix"; + if (m_pkgConfig.options().definePrefix) { + if (tag == prefixVariable) { + std::string_view prefix = pkg.filePath; + prefix = parentPath(prefix); + if (completeBaseName(prefix) == "pkgconfig") { + prefix = parentPath(prefix); + prefix = parentPath(prefix); + } + pkg.oldPrefix = std::string(str); + if (!prefix.empty()) + str = prefix; + return std::string(str); + } else if (pkg.oldPrefix + && str.size() > pkg.oldPrefix->size() + && str.substr(0, pkg.oldPrefix->size()) == *pkg.oldPrefix + && str[pkg.oldPrefix->size()] == '/') { + auto result = pkg.variables["prefix"]; + result += str.substr(pkg.oldPrefix->size()); + return trimAndSubstitute(pkg, result); + } + } + + return trimAndSubstitute(pkg, str); +} + void PcParser::parseStringField( PcPackage &pkg, std::string &field, @@ -765,14 +777,7 @@ void PcParser::parseLine(PcPackage &pkg, std::string_view str) str.remove_prefix(1); // cut '=' str = trimmed(str); - // TODO: support guesstimating of the prefix variable (pkg-config's --define-prefix option) - // from doc: "try to override the value of prefix for each .pc file found with a - // guesstimated value based on the location of the .pc file" - // https://gitlab.freedesktop.org/pkg-config/pkg-config/-/blob/pkg-config-0.29.2/parse.c#L998 - // This option is disabled by default, and Qbs doesn't allow to override it yet, so we can - // ignore this feature for now - - const auto value = trimAndSubstitute(pkg, str); + const auto value = evaluateVariable(pkg, tag, str); if (!pkg.variables.insert({std::string(tag), value}).second) raizeDuplicateVariableException(pkg, tag); } diff --git a/src/lib/pkgconfig/pcparser.h b/src/lib/pkgconfig/pcparser.h index ffdf86aaa..a8a5e5cd4 100644 --- a/src/lib/pkgconfig/pcparser.h +++ b/src/lib/pkgconfig/pcparser.h @@ -55,6 +55,7 @@ public: private: std::string trimAndSubstitute(const PcPackage &pkg, std::string_view str) const; + std::string evaluateVariable(PcPackage &pkg, std::string_view tag, std::string_view str) const; void parseStringField( PcPackage &pkg, std::string &field, diff --git a/src/lib/pkgconfig/pkgconfig.h b/src/lib/pkgconfig/pkgconfig.h index c1cc634e3..d50363e4a 100644 --- a/src/lib/pkgconfig/pkgconfig.h +++ b/src/lib/pkgconfig/pkgconfig.h @@ -59,6 +59,7 @@ public: bool disableUninstalled{true}; // PKG_CONFIG_DISABLE_UNINSTALLED bool staticMode{false}; bool mergeDependencies{true}; + bool definePrefix{false}; VariablesMap globalVariables; VariablesMap systemVariables; }; diff --git a/tests/auto/pkgconfig/testdata/lib/pkgconfig/prefix.pc b/tests/auto/pkgconfig/testdata/lib/pkgconfig/prefix.pc new file mode 100644 index 000000000..64b980803 --- /dev/null +++ b/tests/auto/pkgconfig/testdata/lib/pkgconfig/prefix.pc @@ -0,0 +1,13 @@ +prefix=/usr +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=/usr/include +usrdir=/usrdir + +Name: Prefix test +Description: This tests prefix auto detection +Version: 1.0.0 +Requires: +Libs: -lprefix +Libs.private: -lm +Cflags: -I${includedir} diff --git a/tests/auto/pkgconfig/tst_pkgconfig.cpp b/tests/auto/pkgconfig/tst_pkgconfig.cpp index 220e54e7a..ac94e1c48 100644 --- a/tests/auto/pkgconfig/tst_pkgconfig.cpp +++ b/tests/auto/pkgconfig/tst_pkgconfig.cpp @@ -33,6 +33,7 @@ #include <tools/fileinfo.h> #include <tools/hostosinfo.h> #include <pkgconfig.h> +#include <pcparser.h> #include <jsextensions/pkgconfigjs.h> #include <QJsonArray> @@ -58,6 +59,48 @@ void TestPkgConfig::initTestCase() qPrintable(errorMessage)); } +void TestPkgConfig::fileName() +{ + QCOMPARE(qbs::Internal::fileName(""), ""); + QCOMPARE(qbs::Internal::fileName("file.txt"), "file.txt"); + QCOMPARE(qbs::Internal::fileName("/home/user/file.txt"), "file.txt"); + QCOMPARE(qbs::Internal::fileName("/"), ""); +#if defined(Q_OS_WIN) + QCOMPARE(qbs::Internal::fileName("c:file.txt"), "file.txt"); + QCOMPARE(qbs::Internal::fileName("c:"), ""); +#endif +} + +void TestPkgConfig::completeBaseName() +{ + QCOMPARE(qbs::Internal::completeBaseName(""), ""); + QCOMPARE(qbs::Internal::completeBaseName("file.txt"), "file"); + QCOMPARE(qbs::Internal::completeBaseName("archive.tar.gz"), "archive.tar"); + QCOMPARE(qbs::Internal::completeBaseName("/home/user/file.txt"), "file"); +#if defined(Q_OS_WIN) + QCOMPARE(qbs::Internal::completeBaseName("c:file.txt"), "file"); + QCOMPARE(qbs::Internal::completeBaseName("c:archive.tar.gz"), "archive.tar"); + QCOMPARE(qbs::Internal::completeBaseName("c:"), ""); +#endif +} + +void TestPkgConfig::parentPath() +{ + QCOMPARE(qbs::Internal::parentPath(""), ""); + QCOMPARE(qbs::Internal::parentPath("file.txt"), "."); + QCOMPARE(qbs::Internal::parentPath("/home/user/file.txt"), "/home/user"); + QCOMPARE(qbs::Internal::parentPath("/home/user/"), "/home/user"); + QCOMPARE(qbs::Internal::parentPath("/home"), "/"); + QCOMPARE(qbs::Internal::parentPath("/"), "/"); +#if defined(Q_OS_WIN) + QCOMPARE(qbs::Internal::parentPath("c:/folder/file.txt"), "c:/folder"); + QCOMPARE(qbs::Internal::parentPath("c:/folder/"), "c:/folder"); + QCOMPARE(qbs::Internal::parentPath("c:/folder"), "c:/"); + QCOMPARE(qbs::Internal::parentPath("c:/"), "c:/"); + QCOMPARE(qbs::Internal::parentPath("c:"), "c:"); +#endif +} + void TestPkgConfig::pkgConfig() { QFETCH(QString, pcFileName); @@ -214,4 +257,25 @@ void TestPkgConfig::benchSystem() } } +void TestPkgConfig::prefix() +{ + const auto prefixDir = m_workingDataDir; + const auto libDir = m_workingDataDir + "/lib"; + const auto includeDir = m_workingDataDir + "/include"; + const auto pkgconfigDir = libDir + "/pkgconfig"; + Options options = qbs::Internal::PkgConfigJs::convertOptions( + QProcessEnvironment::systemEnvironment(), {}); + options.definePrefix = true; + options.libDirs.push_back(pkgconfigDir.toStdString()); + PkgConfig pkgConfig(std::move(options)); + const auto &packageOr = pkgConfig.getPackage("prefix"); + QVERIFY(packageOr.isValid()); + const auto &package = packageOr.asPackage(); + QCOMPARE(package.variables.at("prefix"), prefixDir.toStdString()); + QCOMPARE(package.variables.at("exec_prefix"), prefixDir.toStdString()); + QCOMPARE(package.variables.at("libdir"), libDir.toStdString()); + QCOMPARE(package.variables.at("includedir"), includeDir.toStdString()); + QCOMPARE(package.variables.at("usrdir"), "/usrdir"); +} + QTEST_MAIN(TestPkgConfig) diff --git a/tests/auto/pkgconfig/tst_pkgconfig.h b/tests/auto/pkgconfig/tst_pkgconfig.h index 687411862..47654e1ec 100644 --- a/tests/auto/pkgconfig/tst_pkgconfig.h +++ b/tests/auto/pkgconfig/tst_pkgconfig.h @@ -41,9 +41,13 @@ public: private slots: void initTestCase(); + void fileName(); + void completeBaseName(); + void parentPath(); void pkgConfig(); void pkgConfig_data(); void benchSystem(); + void prefix(); private: const QString m_sourceDataDir; |