mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
Fixes many bugs in PkgC by incorporating the 0.0.7 changes from llpkgc. These include: * Variable substitution fixes * Better whitespace handling * Correct handling of single and double quotes * Dedicated parsers for fragment and dependency lists Also adds support for pcfiledir, which was blocked on incorrect whitespace handling in the old parser. Fixes: #27652, #27683
458 lines
12 KiB
C++
458 lines
12 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
|
|
#include "cmPkgConfigResolver.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <iterator>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <cm/optional>
|
|
|
|
#include "cmsys/String.h"
|
|
|
|
#include "cmOutputConverter.h"
|
|
#include "cmPkgConfigParser.h"
|
|
#include "cmStringAlgorithms.h"
|
|
|
|
namespace {
|
|
|
|
bool HasPrefix(std::string const& value, char const* prefix)
|
|
{
|
|
std::size_t const len = std::strlen(prefix);
|
|
return value.size() >= len && value.compare(0, len, prefix) == 0;
|
|
}
|
|
|
|
void AppendFlag(std::string& flagline, std::string const& flag)
|
|
{
|
|
if (!flagline.empty()) {
|
|
flagline += ' ';
|
|
}
|
|
flagline += flag;
|
|
}
|
|
|
|
bool IsSystemPath(std::string const& path,
|
|
std::vector<std::string> const& systemPaths)
|
|
{
|
|
return std::find(systemPaths.begin(), systemPaths.end(), path) !=
|
|
systemPaths.end();
|
|
}
|
|
|
|
std::vector<std::string> DefaultSystemIncludePaths()
|
|
{
|
|
return std::vector<std::string>{ "/usr/include" };
|
|
}
|
|
|
|
std::vector<std::string> DefaultSystemLibraryPaths()
|
|
{
|
|
return std::vector<std::string>{ "/usr/lib" };
|
|
}
|
|
|
|
std::vector<std::string> const& SystemCflags(cmPkgConfigEnv const& env,
|
|
std::vector<std::string>& def)
|
|
{
|
|
if (env.SysCflags) {
|
|
return *env.SysCflags;
|
|
}
|
|
def = DefaultSystemIncludePaths();
|
|
return def;
|
|
}
|
|
|
|
std::vector<std::string> const& SystemLibs(cmPkgConfigEnv const& env,
|
|
std::vector<std::string>& def)
|
|
{
|
|
if (env.SysLibs) {
|
|
return *env.SysLibs;
|
|
}
|
|
def = DefaultSystemLibraryPaths();
|
|
return def;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string cmPkgConfigResolver::VersionReqString(
|
|
cmPkgConfigDependencySpec const& spec)
|
|
{
|
|
switch (spec.Operation) {
|
|
case cmPkgConfigDependencyOperation::None:
|
|
return "";
|
|
case cmPkgConfigDependencyOperation::LessThan:
|
|
return cmStrCat('<', spec.Version);
|
|
case cmPkgConfigDependencyOperation::LessThanEqual:
|
|
return cmStrCat("<=", spec.Version);
|
|
case cmPkgConfigDependencyOperation::Equal:
|
|
return cmStrCat('=', spec.Version);
|
|
case cmPkgConfigDependencyOperation::NotEqual:
|
|
return cmStrCat("!=", spec.Version);
|
|
case cmPkgConfigDependencyOperation::GreaterThanEqual:
|
|
return cmStrCat(">=", spec.Version);
|
|
case cmPkgConfigDependencyOperation::GreaterThan:
|
|
return cmStrCat('>', spec.Version);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
cmPkgConfigResolver::cmPkgConfigResolver(cmPkgConfigParser parser,
|
|
cmPkgConfigEnv const& env,
|
|
std::string const& pcFileDir)
|
|
: Parser{ std::move(parser) }
|
|
, Env{ &env }
|
|
{
|
|
this->Parser.ParseComplete();
|
|
this->ConfigureParser(pcFileDir);
|
|
}
|
|
|
|
cm::optional<cmPkgConfigResolver> cmPkgConfigResolver::Resolve(
|
|
cmPkgConfigParser parser, cmPkgConfigEnv const& env, Strictness strictness,
|
|
std::string const& pcFileDir)
|
|
{
|
|
cmPkgConfigResolver resolver{ std::move(parser), env, pcFileDir };
|
|
if (strictness != Strictness::BestEffort && !resolver.HasRequiredFields()) {
|
|
return {};
|
|
}
|
|
if (strictness == Strictness::Strict && resolver.HasStrictConflicts()) {
|
|
return {};
|
|
}
|
|
return resolver;
|
|
}
|
|
|
|
cm::optional<cmPkgConfigResolver> cmPkgConfigResolver::ResolveStrict(
|
|
cmPkgConfigParser parser, cmPkgConfigEnv const& env,
|
|
std::string const& pcFileDir)
|
|
{
|
|
return Resolve(std::move(parser), env, Strictness::Strict, pcFileDir);
|
|
}
|
|
|
|
cm::optional<cmPkgConfigResolver> cmPkgConfigResolver::ResolvePermissive(
|
|
cmPkgConfigParser parser, cmPkgConfigEnv const& env,
|
|
std::string const& pcFileDir)
|
|
{
|
|
return Resolve(std::move(parser), env, Strictness::Permissive, pcFileDir);
|
|
}
|
|
|
|
cmPkgConfigResolver cmPkgConfigResolver::ResolveBestEffort(
|
|
cmPkgConfigParser parser, cmPkgConfigEnv const& env,
|
|
std::string const& pcFileDir)
|
|
{
|
|
return cmPkgConfigResolver{ std::move(parser), env, pcFileDir };
|
|
}
|
|
|
|
void cmPkgConfigResolver::ConfigureParser(std::string const& pcFileDir)
|
|
{
|
|
if (this->Env->SysrootDir) {
|
|
this->Parser.SetVariable(
|
|
"pc_sysrootdir",
|
|
cmOutputConverter::EscapeForShell(*this->Env->SysrootDir,
|
|
cmOutputConverter::Shell_Flag_IsUnix));
|
|
} else {
|
|
this->Parser.SetVariable("pc_sysrootdir", "/");
|
|
}
|
|
|
|
if (this->Env->TopBuildDir) {
|
|
this->Parser.SetVariable(
|
|
"pc_top_builddir",
|
|
cmOutputConverter::EscapeForShell(*this->Env->TopBuildDir,
|
|
cmOutputConverter::Shell_Flag_IsUnix));
|
|
}
|
|
|
|
this->Parser.SetVariable("pcfiledir",
|
|
cmOutputConverter::EscapeForShell(
|
|
pcFileDir, cmOutputConverter::Shell_Flag_IsUnix));
|
|
}
|
|
|
|
bool cmPkgConfigResolver::HasRequiredFields() const
|
|
{
|
|
return this->Parser.HasProperty("Name") &&
|
|
this->Parser.HasProperty("Description") &&
|
|
this->Parser.HasProperty("Version");
|
|
}
|
|
|
|
bool cmPkgConfigResolver::HasStrictConflicts() const
|
|
{
|
|
return (this->Parser.HasProperty("Cflags") &&
|
|
this->Parser.HasProperty("CFlags")) ||
|
|
(this->Parser.HasProperty("Cflags.private") &&
|
|
this->Parser.HasProperty("CFlags.private"));
|
|
}
|
|
|
|
std::string cmPkgConfigResolver::Literal(std::string const& key) const
|
|
{
|
|
return this->Parser.GetLiteral(key);
|
|
}
|
|
|
|
std::string cmPkgConfigResolver::Name() const
|
|
{
|
|
return this->Literal("Name");
|
|
}
|
|
|
|
std::string cmPkgConfigResolver::Description() const
|
|
{
|
|
return this->Literal("Description");
|
|
}
|
|
|
|
std::string cmPkgConfigResolver::Version() const
|
|
{
|
|
return this->Literal("Version");
|
|
}
|
|
|
|
std::vector<std::string> cmPkgConfigResolver::Fragments(
|
|
std::string const& key) const
|
|
{
|
|
return this->Parser.GetFragments(key);
|
|
}
|
|
|
|
std::vector<cmPkgConfigDependencySpec> cmPkgConfigResolver::Dependencies(
|
|
std::string const& key) const
|
|
{
|
|
return this->Parser.GetDependencies(key);
|
|
}
|
|
|
|
std::vector<cmPkgConfigDependencySpec> cmPkgConfigResolver::Conflicts() const
|
|
{
|
|
return this->Dependencies("Conflicts");
|
|
}
|
|
|
|
std::vector<cmPkgConfigDependencySpec> cmPkgConfigResolver::Provides() const
|
|
{
|
|
return this->Dependencies("Provides");
|
|
}
|
|
|
|
std::vector<cmPkgConfigDependencySpec> cmPkgConfigResolver::Requires(
|
|
bool priv) const
|
|
{
|
|
return this->Dependencies(priv ? "Requires.private" : "Requires");
|
|
}
|
|
|
|
cmPkgConfigCflagsResult cmPkgConfigResolver::Cflags(bool priv) const
|
|
{
|
|
auto flags = this->Fragments(priv ? "Cflags.private" : "Cflags");
|
|
auto alt = this->Fragments(priv ? "CFlags.private" : "CFlags");
|
|
flags.insert(flags.end(), alt.begin(), alt.end());
|
|
return MangleCflags(flags, *this->Env);
|
|
}
|
|
|
|
cmPkgConfigLibsResult cmPkgConfigResolver::Libs(bool priv) const
|
|
{
|
|
return MangleLibs(this->Fragments(priv ? "Libs.private" : "Libs"),
|
|
*this->Env);
|
|
}
|
|
|
|
cmPkgConfigCflagsResult cmPkgConfigResolver::MangleCflags(
|
|
std::vector<std::string> const& flags, cmPkgConfigEnv const& env)
|
|
{
|
|
cmPkgConfigCflagsResult result;
|
|
std::vector<std::string> defaultSystemPaths;
|
|
auto const& systemPaths = SystemCflags(env, defaultSystemPaths);
|
|
|
|
for (auto const& flag : flags) {
|
|
std::string mangled = flag;
|
|
if (HasPrefix(flag, "-I") && env.SysrootDir) {
|
|
mangled = Reroot(flag, "-I", *env.SysrootDir);
|
|
}
|
|
|
|
if (HasPrefix(mangled, "-I")) {
|
|
if (!env.AllowSysCflags &&
|
|
IsSystemPath(mangled.substr(2), systemPaths)) {
|
|
continue;
|
|
}
|
|
AppendFlag(result.Flagline, mangled);
|
|
result.Includes.push_back(mangled);
|
|
} else {
|
|
AppendFlag(result.Flagline, mangled);
|
|
result.CompileOptions.push_back(mangled);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
cmPkgConfigLibsResult cmPkgConfigResolver::MangleLibs(
|
|
std::vector<std::string> const& flags, cmPkgConfigEnv const& env)
|
|
{
|
|
cmPkgConfigLibsResult result;
|
|
std::vector<std::string> defaultSystemPaths;
|
|
auto const& systemPaths = SystemLibs(env, defaultSystemPaths);
|
|
|
|
for (auto const& flag : flags) {
|
|
std::string mangled = flag;
|
|
if (HasPrefix(flag, "-L") && env.SysrootDir) {
|
|
mangled = Reroot(flag, "-L", *env.SysrootDir);
|
|
}
|
|
|
|
if (HasPrefix(mangled, "-L")) {
|
|
if (!env.AllowSysLibs && IsSystemPath(mangled.substr(2), systemPaths)) {
|
|
continue;
|
|
}
|
|
AppendFlag(result.Flagline, mangled);
|
|
result.LibDirs.push_back(mangled);
|
|
} else if (HasPrefix(mangled, "-l")) {
|
|
AppendFlag(result.Flagline, mangled);
|
|
result.LibNames.push_back(mangled);
|
|
} else {
|
|
AppendFlag(result.Flagline, mangled);
|
|
result.LinkOptions.push_back(mangled);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string cmPkgConfigResolver::Reroot(std::string const& flag,
|
|
char const* prefix,
|
|
std::string const& sysroot)
|
|
{
|
|
std::string result = prefix;
|
|
result += sysroot;
|
|
result += flag.substr(std::strlen(prefix));
|
|
return result;
|
|
}
|
|
|
|
void cmPkgConfigResolver::ReplaceSep(std::string& list)
|
|
{
|
|
#ifndef _WIN32
|
|
std::replace(list.begin(), list.end(), ':', ';');
|
|
#else
|
|
static_cast<void>(list);
|
|
#endif
|
|
}
|
|
|
|
bool cmPkgConfigResolver::CheckVersion(
|
|
cmPkgConfigDependencySpec const& desired, std::string const& provided)
|
|
{
|
|
if (desired.Operation == cmPkgConfigDependencyOperation::None) {
|
|
return true;
|
|
}
|
|
|
|
auto check_with_op = [&](int comp) -> bool {
|
|
switch (desired.Operation) {
|
|
case cmPkgConfigDependencyOperation::Equal:
|
|
return comp == 0;
|
|
case cmPkgConfigDependencyOperation::NotEqual:
|
|
return comp != 0;
|
|
case cmPkgConfigDependencyOperation::GreaterThan:
|
|
return comp < 0;
|
|
case cmPkgConfigDependencyOperation::GreaterThanEqual:
|
|
return comp <= 0;
|
|
case cmPkgConfigDependencyOperation::LessThan:
|
|
return comp > 0;
|
|
case cmPkgConfigDependencyOperation::LessThanEqual:
|
|
return comp >= 0;
|
|
default:
|
|
return true;
|
|
}
|
|
};
|
|
|
|
if (desired.Version == provided) {
|
|
return check_with_op(0);
|
|
}
|
|
|
|
auto a_cur = desired.Version.begin();
|
|
auto a_end = desired.Version.end();
|
|
|
|
auto b_cur = provided.begin();
|
|
auto b_end = provided.end();
|
|
|
|
while (a_cur != a_end && b_cur != b_end) {
|
|
while (a_cur != a_end && !cmsysString_isalnum(*a_cur) && *a_cur != '~') {
|
|
++a_cur;
|
|
}
|
|
|
|
while (b_cur != b_end && !cmsysString_isalnum(*b_cur) && *b_cur != '~') {
|
|
++b_cur;
|
|
}
|
|
|
|
if (a_cur == a_end || b_cur == b_end) {
|
|
break;
|
|
}
|
|
|
|
if (*a_cur == '~' || *b_cur == '~') {
|
|
if (*a_cur != '~') {
|
|
return check_with_op(1);
|
|
}
|
|
|
|
if (*b_cur != '~') {
|
|
return check_with_op(-1);
|
|
}
|
|
|
|
++a_cur;
|
|
++b_cur;
|
|
continue;
|
|
}
|
|
|
|
auto a_seg = a_cur;
|
|
auto b_seg = b_cur;
|
|
bool is_num;
|
|
|
|
if (cmsysString_isdigit(*a_cur)) {
|
|
is_num = true;
|
|
while (a_cur != a_end && cmsysString_isdigit(*a_cur)) {
|
|
++a_cur;
|
|
}
|
|
|
|
while (b_cur != b_end && cmsysString_isdigit(*b_cur)) {
|
|
++b_cur;
|
|
}
|
|
|
|
} else {
|
|
is_num = false;
|
|
while (a_cur != a_end && cmsysString_isalpha(*a_cur)) {
|
|
++a_cur;
|
|
}
|
|
|
|
while (b_cur != b_end && cmsysString_isalpha(*b_cur)) {
|
|
++b_cur;
|
|
}
|
|
}
|
|
|
|
auto a_len = std::distance(a_seg, a_cur);
|
|
auto b_len = std::distance(b_seg, b_cur);
|
|
|
|
if (!b_len) {
|
|
return check_with_op(is_num ? 1 : -1);
|
|
}
|
|
|
|
if (is_num) {
|
|
while (a_seg != a_cur && *a_seg == '0') {
|
|
++a_seg;
|
|
}
|
|
|
|
while (b_seg != b_cur && *b_seg == '0') {
|
|
++b_seg;
|
|
}
|
|
|
|
a_len = std::distance(a_seg, a_cur);
|
|
b_len = std::distance(b_seg, b_cur);
|
|
|
|
if (a_len != b_len) {
|
|
return check_with_op(a_len > b_len ? 1 : -1);
|
|
}
|
|
|
|
auto cmp = std::memcmp(&*a_seg, &*b_seg, a_len);
|
|
if (cmp) {
|
|
return check_with_op(cmp);
|
|
}
|
|
} else {
|
|
auto cmp = std::memcmp(&*a_seg, &*b_seg, std::min(a_len, b_len));
|
|
if (cmp) {
|
|
return check_with_op(cmp);
|
|
}
|
|
|
|
if (a_len != b_len) {
|
|
return check_with_op(a_len > b_len ? 1 : -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (a_cur == a_end) {
|
|
if (b_cur == b_end) {
|
|
return check_with_op(0);
|
|
}
|
|
return check_with_op(-1);
|
|
}
|
|
|
|
return check_with_op(1);
|
|
}
|