You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cmake/Source/cmPkgConfigResolver.cxx

871 lines
21 KiB

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmPkgConfigResolver.h"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <iterator>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <cm/optional>
#include <cm/string_view>
#include "cmPkgConfigParser.h"
namespace {
void TrimBack(std::string& str)
{
if (!str.empty()) {
auto it = str.end() - 1;
for (; std::isspace(*it); --it) {
if (it == str.begin()) {
str.clear();
return;
}
}
str.erase(++it, str.end());
}
}
std::string AppendAndTrim(std::string& str, cm::string_view sv)
{
auto size = str.length();
str += sv;
if (str.empty()) {
return {};
}
auto begin = str.begin() + size;
auto cur = str.end() - 1;
while (cur != begin && std::isspace(*cur)) {
--cur;
}
if (std::isspace(*cur)) {
return {};
}
return { &*begin, static_cast<std::size_t>(cur - begin) + 1 };
}
} // namespace
std::string cmPkgConfigResult::StrOrDefault(const std::string& key,
cm::string_view def)
{
auto it = Keywords.find(key);
return it == Keywords.end() ? std::string{ def } : it->second;
};
std::string cmPkgConfigResult::Name()
{
return StrOrDefault("Name");
}
std::string cmPkgConfigResult::Description()
{
return StrOrDefault("Description");
}
std::string cmPkgConfigResult::Version()
{
return StrOrDefault("Version");
}
std::vector<cmPkgConfigDependency> cmPkgConfigResult::Conflicts()
{
auto it = Keywords.find("Conflicts");
if (it == Keywords.end()) {
return {};
}
return cmPkgConfigResolver::ParseDependencies(it->second);
}
std::vector<cmPkgConfigDependency> cmPkgConfigResult::Provides()
{
auto it = Keywords.find("Provides");
if (it == Keywords.end()) {
return {};
}
return cmPkgConfigResolver::ParseDependencies(it->second);
}
std::vector<cmPkgConfigDependency> cmPkgConfigResult::Requires(bool priv)
{
auto it = Keywords.find(priv ? "Requires.private" : "Requires");
if (it == Keywords.end()) {
return {};
}
return cmPkgConfigResolver::ParseDependencies(it->second);
}
cmPkgConfigCflagsResult cmPkgConfigResult::Cflags(bool priv)
{
std::string cflags;
auto it = Keywords.find(priv ? "Cflags.private" : "Cflags");
if (it != Keywords.end()) {
cflags += it->second;
}
it = Keywords.find(priv ? "CFlags.private" : "CFlags");
if (it != Keywords.end()) {
if (!cflags.empty()) {
cflags += " ";
}
cflags += it->second;
}
auto tokens = cmPkgConfigResolver::TokenizeFlags(cflags);
if (env.AllowSysCflags) {
if (env.SysrootDir) {
return cmPkgConfigResolver::MangleCflags(tokens, *env.SysrootDir);
}
return cmPkgConfigResolver::MangleCflags(tokens);
}
if (env.SysCflags) {
if (env.SysrootDir) {
return cmPkgConfigResolver::MangleCflags(tokens, *env.SysrootDir,
*env.SysCflags);
}
return cmPkgConfigResolver::MangleCflags(tokens, *env.SysCflags);
}
if (env.SysrootDir) {
return cmPkgConfigResolver::MangleCflags(
tokens, *env.SysrootDir, std::vector<std::string>{ "/usr/include" });
}
return cmPkgConfigResolver::MangleCflags(
tokens, std::vector<std::string>{ "/usr/include" });
}
cmPkgConfigLibsResult cmPkgConfigResult::Libs(bool priv)
{
auto it = Keywords.find(priv ? "Libs.private" : "Libs");
if (it == Keywords.end()) {
return cmPkgConfigLibsResult();
}
auto tokens = cmPkgConfigResolver::TokenizeFlags(it->second);
if (env.AllowSysLibs) {
if (env.SysrootDir) {
return cmPkgConfigResolver::MangleLibs(tokens, *env.SysrootDir);
}
return cmPkgConfigResolver::MangleLibs(tokens);
}
if (env.SysLibs) {
if (env.SysrootDir) {
return cmPkgConfigResolver::MangleLibs(tokens, *env.SysrootDir,
*env.SysLibs);
}
return cmPkgConfigResolver::MangleLibs(tokens, *env.SysLibs);
}
if (env.SysrootDir) {
return cmPkgConfigResolver::MangleLibs(
tokens, *env.SysrootDir, std::vector<std::string>{ "/usr/lib" });
}
return cmPkgConfigResolver::MangleLibs(
tokens, std::vector<std::string>{ "/usr/lib" });
}
void cmPkgConfigResolver::ReplaceSep(std::string& list)
{
#ifndef _WIN32
std::replace(list.begin(), list.end(), ':', ';');
#else
static_cast<void>(list); // Unused parameter
#endif
}
cm::optional<cmPkgConfigResult> cmPkgConfigResolver::ResolveStrict(
const std::vector<cmPkgConfigEntry>& entries, cmPkgConfigEnv env)
{
cm::optional<cmPkgConfigResult> result;
cmPkgConfigResult config;
auto& keys = config.Keywords;
if (env.SysrootDir) {
config.Variables["pc_sysrootdir"] = *env.SysrootDir;
} else {
config.Variables["pc_sysrootdir"] = "/";
}
if (env.TopBuildDir) {
config.Variables["pc_top_builddir"] = *env.TopBuildDir;
}
config.env = std::move(env);
for (const auto& entry : entries) {
std::string key(entry.Key);
if (entry.IsVariable) {
if (config.Variables.find(key) != config.Variables.end()) {
return result;
}
auto var = HandleVariableStrict(entry, config.Variables);
if (!var) {
return result;
}
config.Variables[key] = *var;
} else {
if (key == "Cflags" && keys.find("CFlags") != keys.end()) {
return result;
}
if (key == "CFlags" && keys.find("Cflags") != keys.end()) {
return result;
}
if (key == "Cflags.private" &&
keys.find("CFlags.private") != keys.end()) {
return result;
}
if (key == "CFlags.private" &&
keys.find("Cflags.private") != keys.end()) {
return result;
}
if (keys.find(key) != keys.end()) {
return result;
}
keys[key] = HandleKeyword(entry, config.Variables);
}
}
if (keys.find("Name") == keys.end() ||
keys.find("Description") == keys.end() ||
keys.find("Version") == keys.end()) {
return result;
}
result = std::move(config);
return result;
}
cm::optional<cmPkgConfigResult> cmPkgConfigResolver::ResolvePermissive(
const std::vector<cmPkgConfigEntry>& entries, cmPkgConfigEnv env)
{
cm::optional<cmPkgConfigResult> result;
cmPkgConfigResult config = ResolveBestEffort(entries, std::move(env));
const auto& keys = config.Keywords;
if (keys.find("Name") == keys.end() ||
keys.find("Description") == keys.end() ||
keys.find("Version") == keys.end()) {
return result;
}
result = std::move(config);
return result;
}
cmPkgConfigResult cmPkgConfigResolver::ResolveBestEffort(
const std::vector<cmPkgConfigEntry>& entries, cmPkgConfigEnv env)
{
cmPkgConfigResult result;
if (env.SysrootDir) {
result.Variables["pc_sysrootdir"] = *env.SysrootDir;
} else {
result.Variables["pc_sysrootdir"] = "/";
}
if (env.TopBuildDir) {
result.Variables["pc_top_builddir"] = *env.TopBuildDir;
}
result.env = std::move(env);
for (const auto& entry : entries) {
std::string key(entry.Key);
if (entry.IsVariable) {
result.Variables[key] =
HandleVariablePermissive(entry, result.Variables);
} else {
result.Keywords[key] += HandleKeyword(entry, result.Variables);
}
}
return result;
}
std::string cmPkgConfigResolver::HandleVariablePermissive(
const cmPkgConfigEntry& entry,
const std::unordered_map<std::string, std::string>& variables)
{
std::string result;
for (const auto& segment : entry.Val) {
if (!segment.IsVariable) {
result += segment.Data;
} else if (entry.Key != segment.Data) {
auto it = variables.find(std::string{ segment.Data });
if (it != variables.end()) {
result += it->second;
}
}
}
TrimBack(result);
return result;
}
cm::optional<std::string> cmPkgConfigResolver::HandleVariableStrict(
const cmPkgConfigEntry& entry,
const std::unordered_map<std::string, std::string>& variables)
{
cm::optional<std::string> result;
std::string value;
for (const auto& segment : entry.Val) {
if (!segment.IsVariable) {
value += segment.Data;
} else if (entry.Key == segment.Data) {
return result;
} else {
auto it = variables.find(std::string{ segment.Data });
if (it != variables.end()) {
value += it->second;
} else {
return result;
}
}
}
TrimBack(value);
result = std::move(value);
return result;
}
std::string cmPkgConfigResolver::HandleKeyword(
const cmPkgConfigEntry& entry,
const std::unordered_map<std::string, std::string>& variables)
{
std::string result;
for (const auto& segment : entry.Val) {
if (!segment.IsVariable) {
result += segment.Data;
} else {
auto it = variables.find(std::string{ segment.Data });
if (it != variables.end()) {
result += it->second;
}
}
}
TrimBack(result);
return result;
}
std::vector<cm::string_view> cmPkgConfigResolver::TokenizeFlags(
const std::string& flagline)
{
std::vector<cm::string_view> result;
auto it = flagline.begin();
while (it != flagline.end() && std::isspace(*it)) {
++it;
}
while (it != flagline.end()) {
const char* start = &(*it);
std::size_t len = 0;
for (; it != flagline.end() && !std::isspace(*it); ++it) {
++len;
}
for (; it != flagline.end() && std::isspace(*it); ++it) {
++len;
}
result.emplace_back(start, len);
}
return result;
}
cmPkgConfigCflagsResult cmPkgConfigResolver::MangleCflags(
const std::vector<cm::string_view>& flags)
{
cmPkgConfigCflagsResult result;
for (auto flag : flags) {
if (flag.rfind("-I", 0) == 0) {
result.Includes.emplace_back(AppendAndTrim(result.Flagline, flag));
} else {
result.CompileOptions.emplace_back(AppendAndTrim(result.Flagline, flag));
}
}
return result;
}
cmPkgConfigCflagsResult cmPkgConfigResolver::MangleCflags(
const std::vector<cm::string_view>& flags, const std::string& sysroot)
{
cmPkgConfigCflagsResult result;
for (auto flag : flags) {
if (flag.rfind("-I", 0) == 0) {
std::string reroot = Reroot(flag, "-I", sysroot);
result.Includes.emplace_back(AppendAndTrim(result.Flagline, reroot));
} else {
result.CompileOptions.emplace_back(AppendAndTrim(result.Flagline, flag));
}
}
return result;
}
cmPkgConfigCflagsResult cmPkgConfigResolver::MangleCflags(
const std::vector<cm::string_view>& flags,
const std::vector<std::string>& syspaths)
{
cmPkgConfigCflagsResult result;
for (auto flag : flags) {
if (flag.rfind("-I", 0) == 0) {
cm::string_view noprefix{ flag.data() + 2, flag.size() - 2 };
if (std::all_of(syspaths.begin(), syspaths.end(),
[&](const std::string& path) {
return noprefix.rfind(path, 0) == noprefix.npos;
})) {
result.Includes.emplace_back(AppendAndTrim(result.Flagline, flag));
}
} else {
result.CompileOptions.emplace_back(AppendAndTrim(result.Flagline, flag));
}
}
return result;
}
cmPkgConfigCflagsResult cmPkgConfigResolver::MangleCflags(
const std::vector<cm::string_view>& flags, const std::string& sysroot,
const std::vector<std::string>& syspaths)
{
cmPkgConfigCflagsResult result;
for (auto flag : flags) {
if (flag.rfind("-I", 0) == 0) {
std::string reroot = Reroot(flag, "-I", sysroot);
cm::string_view noprefix{ reroot.data() + 2, reroot.size() - 2 };
if (std::all_of(syspaths.begin(), syspaths.end(),
[&](const std::string& path) {
return noprefix.rfind(path, 0) == noprefix.npos;
})) {
result.Includes.emplace_back(AppendAndTrim(result.Flagline, reroot));
}
} else {
result.CompileOptions.emplace_back(AppendAndTrim(result.Flagline, flag));
}
}
return result;
}
cmPkgConfigLibsResult cmPkgConfigResolver::MangleLibs(
const std::vector<cm::string_view>& flags)
{
cmPkgConfigLibsResult result;
for (auto flag : flags) {
if (flag.rfind("-L", 0) == 0) {
result.LibDirs.emplace_back(AppendAndTrim(result.Flagline, flag));
} else if (flag.rfind("-l", 0) == 0) {
result.LibNames.emplace_back(AppendAndTrim(result.Flagline, flag));
} else {
result.LinkOptions.emplace_back(AppendAndTrim(result.Flagline, flag));
}
}
return result;
}
cmPkgConfigLibsResult cmPkgConfigResolver::MangleLibs(
const std::vector<cm::string_view>& flags, const std::string& sysroot)
{
cmPkgConfigLibsResult result;
for (auto flag : flags) {
if (flag.rfind("-L", 0) == 0) {
std::string reroot = Reroot(flag, "-L", sysroot);
result.LibDirs.emplace_back(AppendAndTrim(result.Flagline, reroot));
} else if (flag.rfind("-l", 0) == 0) {
result.LibNames.emplace_back(AppendAndTrim(result.Flagline, flag));
} else {
result.LinkOptions.emplace_back(AppendAndTrim(result.Flagline, flag));
}
}
return result;
}
cmPkgConfigLibsResult cmPkgConfigResolver::MangleLibs(
const std::vector<cm::string_view>& flags,
const std::vector<std::string>& syspaths)
{
cmPkgConfigLibsResult result;
for (auto flag : flags) {
if (flag.rfind("-L", 0) == 0) {
cm::string_view noprefix{ flag.data() + 2, flag.size() - 2 };
if (std::all_of(syspaths.begin(), syspaths.end(),
[&](const std::string& path) {
return noprefix.rfind(path, 0) == noprefix.npos;
})) {
result.LibDirs.emplace_back(AppendAndTrim(result.Flagline, flag));
}
} else if (flag.rfind("-l", 0) == 0) {
result.LibNames.emplace_back(AppendAndTrim(result.Flagline, flag));
} else {
result.LinkOptions.emplace_back(AppendAndTrim(result.Flagline, flag));
}
}
return result;
}
cmPkgConfigLibsResult cmPkgConfigResolver::MangleLibs(
const std::vector<cm::string_view>& flags, const std::string& sysroot,
const std::vector<std::string>& syspaths)
{
cmPkgConfigLibsResult result;
for (auto flag : flags) {
if (flag.rfind("-L", 0) == 0) {
std::string reroot = Reroot(flag, "-L", sysroot);
cm::string_view noprefix{ reroot.data() + 2, reroot.size() - 2 };
if (std::all_of(syspaths.begin(), syspaths.end(),
[&](const std::string& path) {
return noprefix.rfind(path, 0) == noprefix.npos;
})) {
result.LibDirs.emplace_back(AppendAndTrim(result.Flagline, reroot));
}
} else if (flag.rfind("-l", 0) == 0) {
result.LibNames.emplace_back(AppendAndTrim(result.Flagline, flag));
} else {
result.LinkOptions.emplace_back(AppendAndTrim(result.Flagline, flag));
}
}
return result;
}
std::string cmPkgConfigResolver::Reroot(cm::string_view flag,
cm::string_view prefix,
const std::string& sysroot)
{
std::string result = std::string{ prefix };
result += sysroot;
result += cm::string_view{ flag.data() + prefix.length(),
flag.size() - prefix.length() };
return result;
}
cmPkgConfigVersionReq cmPkgConfigResolver::ParseVersion(
std::string::const_iterator& cur, std::string::const_iterator end)
{
cmPkgConfigVersionReq result;
if (*cur == '=') {
result.Operation = result.EQ;
++cur;
} else if (*cur == '>') {
++cur;
if (cur == end) {
result.Operation = result.GT;
return result;
}
if (*cur == '=') {
result.Operation = result.GT_EQ;
++cur;
} else {
result.Operation = result.GT;
}
} else if (*cur == '<') {
++cur;
if (cur == end) {
result.Operation = result.LT;
return result;
}
if (*cur == '=') {
result.Operation = result.LT_EQ;
++cur;
} else {
result.Operation = result.LT;
}
} else if (*cur == '!') {
++cur;
if (cur == end) {
result.Operation = result.ANY;
return result;
}
if (*cur == '=') {
result.Operation = result.NEQ;
++cur;
} else {
result.Operation = result.ANY;
}
}
for (;; ++cur) {
if (cur == end) {
return result;
}
if (!std::isspace(*cur)) {
break;
}
}
for (; cur != end && !std::isspace(*cur) && *cur != ','; ++cur) {
result.Version += *cur;
}
return result;
}
std::vector<cmPkgConfigDependency> cmPkgConfigResolver::ParseDependencies(
const std::string& deps)
{
std::vector<cmPkgConfigDependency> result;
auto cur = deps.begin();
auto end = deps.end();
while (cur != end) {
while ((std::isspace(*cur) || *cur == ',')) {
if (++cur == end) {
return result;
}
}
result.emplace_back();
auto& dep = result.back();
while (!std::isspace(*cur) && *cur != ',') {
dep.Name += *cur;
if (++cur == end) {
return result;
}
}
auto in_operator = [&]() -> bool {
for (;; ++cur) {
if (cur == end) {
return false;
}
if (*cur == '>' || *cur == '=' || *cur == '<' || *cur == '!') {
return true;
}
if (!std::isspace(*cur)) {
return false;
}
}
};
if (!in_operator()) {
continue;
}
dep.VerReq = ParseVersion(cur, end);
}
return result;
}
bool cmPkgConfigResolver::CheckVersion(const cmPkgConfigVersionReq& desired,
const std::string& provided)
{
if (desired.Operation == cmPkgConfigVersionReq::ANY) {
return true;
}
// https://blog.jasonantman.com/2014/07/how-yum-and-rpm-compare-versions/
auto check_with_op = [&](int comp) -> bool {
switch (desired.Operation) {
case cmPkgConfigVersionReq::EQ:
return comp == 0;
case cmPkgConfigVersionReq::NEQ:
return comp != 0;
case cmPkgConfigVersionReq::GT:
return comp < 0;
case cmPkgConfigVersionReq::GT_EQ:
return comp <= 0;
case cmPkgConfigVersionReq::LT:
return comp > 0;
case cmPkgConfigVersionReq::LT_EQ:
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 && !std::isalnum(*a_cur) && *a_cur != '~') {
++a_cur;
}
while (b_cur != b_end && !std::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 (std::isdigit(*a_cur)) {
is_num = true;
while (a_cur != a_end && std::isdigit(*a_cur)) {
++a_cur;
}
while (b_cur != b_end && std::isdigit(*b_cur)) {
++b_cur;
}
} else {
is_num = false;
while (a_cur != a_end && std::isalpha(*a_cur)) {
++a_cur;
}
while (b_cur != b_end && std::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);
}
cmPkgConfigVersionReq cmPkgConfigResolver::ParseVersion(
const std::string& version)
{
cmPkgConfigVersionReq result;
auto cur = version.begin();
auto end = version.end();
if (cur == end) {
result.Operation = cmPkgConfigVersionReq::EQ;
return result;
}
result = ParseVersion(cur, end);
cur = version.begin();
if (*cur != '=' && *cur != '!' && *cur != '<' && *cur != '>') {
result.Operation = cmPkgConfigVersionReq::EQ;
}
return result;
}