From 38fa645e73f7da9ef9d4ea6cc970db1136072c87 Mon Sep 17 00:00:00 2001 From: Vito Gamberini Date: Wed, 24 Jun 2026 12:06:21 -0400 Subject: [PATCH] PkgC: Correctly handle various pkg-config edge cases 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 --- Source/cmCMakePkgConfigCommand.cxx | 209 ++-- Source/cmPkgConfigParser.cxx | 626 ++++++++++-- Source/cmPkgConfigParser.h | 255 ++++- Source/cmPkgConfigResolver.cxx | 944 +++++------------- Source/cmPkgConfigResolver.h | 153 +-- Tests/Fuzzing/cmPkgConfigParserFuzzer.cxx | 25 +- .../cmake_pkg_config/ExtractBuiltinDirs.cmake | 26 + .../PackageRoot/builtindirs.pc | 9 + .../cmake_pkg_config/PackageRoot/invalid.pc | 2 +- .../cmake_pkg_config/RunCMakeTest.cmake | 1 + 10 files changed, 1270 insertions(+), 980 deletions(-) create mode 100644 Tests/RunCMake/cmake_pkg_config/ExtractBuiltinDirs.cmake create mode 100644 Tests/RunCMake/cmake_pkg_config/PackageRoot/builtindirs.pc diff --git a/Source/cmCMakePkgConfigCommand.cxx b/Source/cmCMakePkgConfigCommand.cxx index b30e7a846e..e6b1a52e7f 100644 --- a/Source/cmCMakePkgConfigCommand.cxx +++ b/Source/cmCMakePkgConfigCommand.cxx @@ -451,27 +451,64 @@ struct ImportEnv bool required; bool quiet; bool exact; - bool err; CommonArguments::StrictnessType strictness; - cmExecutionStatus& status; }; -void warn_or_error(std::string const& err, ImportEnv& imEnv) +struct ImportState +{ + cmExecutionStatus& status; + bool err; +}; + +void warn_or_error(std::string const& err, ImportEnv imEnv, ImportState& state) { if (imEnv.required) { - imEnv.status.SetError(err); + state.status.SetError(err); cmSystemTools::SetFatalErrorOccurred(); } else if (!imEnv.quiet) { - imEnv.status.GetMakefile().IssueMessage(MessageType::WARNING, err); + state.status.GetMakefile().IssueMessage(MessageType::WARNING, err); } - imEnv.err = true; + state.err = true; } -cm::optional ReadPackage(std::string const& package, - ImportEnv& imEnv, - cmPkgConfigEnv& pcEnv) +cm::optional MakeVersionRequirement( + std::string const& version) { - cm::optional result; + std::string requirement = cmTrimWhitespace(version); + std::string expression = "cmake_pkg_config_version"; + expression += ' '; + + if (requirement.empty()) { + expression += '='; + } else { + char const first = requirement.front(); + if (first != '<' && first != '>' && first != '=' && first != '!') { + expression += "= "; + } + expression += requirement; + } + + cmPkgConfigDependencyParser parser; + auto err = parser.Parse(expression.data(), expression.size()); + if (err != PDEP_OK || parser.Finish() != PDEP_OK) { + return {}; + } + + auto const& dependencies = parser.Dependencies(); + if (dependencies.size() != 1 || + dependencies.front().Name != "cmake_pkg_config_version") { + return {}; + } + + return dependencies.front(); +} + +cm::optional ReadPackage(std::string const& package, + ImportEnv imEnv, + cmPkgConfigEnv const& pcEnv, + ImportState& state) +{ + cm::optional result; cm::filesystem::path path{ package }; if (path.extension() == ".pc") { @@ -510,7 +547,7 @@ cm::optional ReadPackage(std::string const& package, if (!ifs) { warn_or_error(cmStrCat("Could not open file '", path.string(), '\''), - imEnv); + imEnv, state); return result; } @@ -520,7 +557,7 @@ cm::optional ReadPackage(std::string const& package, // Shouldn't have hit eof on previous read, should hit eof now if (ifs.fail() || ifs.eof() || ifs.get() != EOF) { warn_or_error(cmStrCat("Error while reading file '", path.string(), '\''), - imEnv); + imEnv, state); return result; } @@ -530,38 +567,45 @@ cm::optional ReadPackage(std::string const& package, auto err = parser.Finish(buf.get(), len); if (imEnv.strictness != StrictnessType::STRICTNESS_BEST_EFFORT && - err != PCE_OK) { + err != PFCE_OK) { warn_or_error(cmStrCat("Parsing failed for file '", path.string(), '\''), - imEnv); + imEnv, state); return result; } + std::string const pcFileDir = cmSystemTools::GetFilenamePath( + cmSystemTools::CollapseFullPath(path.string())); + if (imEnv.strictness == StrictnessType::STRICTNESS_STRICT) { - result = cmPkgConfigResolver::ResolveStrict(parser.Data(), pcEnv); + result = + cmPkgConfigResolver::ResolveStrict(std::move(parser), pcEnv, pcFileDir); } else if (imEnv.strictness == StrictnessType::STRICTNESS_PERMISSIVE) { - result = cmPkgConfigResolver::ResolvePermissive(parser.Data(), pcEnv); + result = cmPkgConfigResolver::ResolvePermissive(std::move(parser), pcEnv, + pcFileDir); } else { - result = cmPkgConfigResolver::ResolveBestEffort(parser.Data(), pcEnv); + result = cmPkgConfigResolver::ResolveBestEffort(std::move(parser), pcEnv, + pcFileDir); } if (!result) { warn_or_error( - cmStrCat("Resolution failed for file '", path.string(), '\''), imEnv); + cmStrCat("Resolution failed for file '", path.string(), '\''), imEnv, + state); } return result; } -cm::optional ImportPackage( +cm::optional ImportPackage( std::string const& package, cm::optional version, - ImportEnv& imEnv, cmPkgConfigEnv& pcEnv) + ImportEnv imEnv, cmPkgConfigEnv const& pcEnv, ImportState& state) { - auto result = ReadPackage(package, imEnv, pcEnv); + auto result = ReadPackage(package, imEnv, pcEnv, state); if (!result) { - if (!imEnv.err) { + if (!state.err) { warn_or_error(cmStrCat("Could not find pkg-config: '", package, '\''), - imEnv); + imEnv, state); } return result; } @@ -570,53 +614,64 @@ cm::optional ImportPackage( std::string ver; if (version) { - ver = cmPkgConfigResolver::ParseVersion(*version).Version; + auto req = MakeVersionRequirement(*version); + if (!req) { + warn_or_error( + cmStrCat("Invalid version requirement '", *version, '\''), imEnv, + state); + return {}; + } + ver = req->Version; } if (ver != result->Version()) { warn_or_error( cmStrCat("Package '", package, "' version '", result->Version(), "' does not meet exact version requirement '", ver, '\''), - imEnv); + imEnv, state); return {}; } } else if (version) { - auto rv = cmPkgConfigResolver::ParseVersion(*version); - if (!cmPkgConfigResolver::CheckVersion(rv, result->Version())) { + auto req = MakeVersionRequirement(*version); + if (!req) { + warn_or_error(cmStrCat("Invalid version requirement '", *version, '\''), + imEnv, state); + return {}; + } + if (!cmPkgConfigResolver::CheckVersion(*req, result->Version())) { warn_or_error( cmStrCat("Package '", package, "' version '", result->Version(), "' does not meet version requirement '", *version, '\''), - imEnv); + imEnv, state); return {}; } } - result->env = &pcEnv; return result; } struct pkgStackEntry { - cmPkgConfigVersionReq ver; + cmPkgConfigDependencySpec Dependency; std::string parent; }; -cm::optional ImportPackage( +cm::optional ImportPackage( std::string const& package, std::vector const& reqs, - ImportEnv& imEnv, cmPkgConfigEnv& pcEnv) + ImportEnv imEnv, cmPkgConfigEnv const& pcEnv, ImportState& state) { - auto result = ReadPackage(package, imEnv, pcEnv); + auto result = ReadPackage(package, imEnv, pcEnv, state); if (!result) { - if (!imEnv.err) { + if (!state.err) { std::string req_str = cmStrCat('\'', reqs.begin()->parent, '\''); for (auto it = reqs.begin() + 1; it != reqs.end(); ++it) { req_str = cmStrCat(req_str, ", '", it->parent, '\''); } warn_or_error(cmStrCat("Could not find pkg-config: '", package, "' required by: ", req_str), - imEnv); + imEnv, state); } return result; } @@ -624,16 +679,17 @@ cm::optional ImportPackage( auto ver = result->Version(); for (auto const& req : reqs) { - if (!cmPkgConfigResolver::CheckVersion(req.ver, ver)) { - warn_or_error(cmStrCat("Package '", package, "' version '", ver, - "' does not meet version requirement '", - req.ver.string(), "' of '", req.parent, '\''), - imEnv); + if (!cmPkgConfigResolver::CheckVersion(req.Dependency, ver)) { + warn_or_error( + cmStrCat("Package '", package, "' version '", ver, + "' does not meet version requirement '", + cmPkgConfigResolver::VersionReqString(req.Dependency), + "' of '", req.parent, '\''), + imEnv, state); return {}; } } - result->env = &pcEnv; return result; } @@ -679,8 +735,7 @@ cm::optional> HandleCommon( } return std::pair{ - pcEnv, - { args.Required, args.Quiet, args.Exact, false, args.Strictness, status } + pcEnv, { args.Required, args.Quiet, args.Exact, args.Strictness } }; } @@ -714,10 +769,11 @@ bool HandleExtractCommand(std::vector const& args, return !parsedArgs.Required; } auto& pcEnv = maybeEnv->first; - auto& imEnv = maybeEnv->second; + auto const& imEnv = maybeEnv->second; + ImportState state{ status, false }; - auto maybePackage = - ImportPackage(*parsedArgs.Package, parsedArgs.Version, imEnv, pcEnv); + auto maybePackage = ImportPackage(*parsedArgs.Package, parsedArgs.Version, + imEnv, pcEnv, state); if (!maybePackage) { return !parsedArgs.Required; } @@ -745,7 +801,7 @@ bool HandleExtractCommand(std::vector const& args, mf.AddDefinition("CMAKE_PKG_CONFIG_VERSION", package.Version()); auto make_list = [&](char const* def, - std::vector const& deps) { + std::vector const& deps) { std::vector vec; vec.reserve(deps.size()); @@ -800,8 +856,8 @@ using pkgStack = std::unordered_map>; using pkgProviders = std::unordered_map; cmTarget* CreateCMakeTarget(std::string const& name, std::string const& prefix, - cmPkgConfigResult& pkg, pkgProviders& providers, - cmMakefile& mf) + cmPkgConfigResolver const& pkg, + pkgProviders const& providers, cmMakefile& mf) { auto* tgt = mf.AddForeignTarget("pkgcfg", cmStrCat(prefix, name)); @@ -824,7 +880,7 @@ cmTarget* CreateCMakeTarget(std::string const& name, std::string const& prefix, tgt->AppendProperty("INTERFACE_COMPILE_OPTIONS", cmList::to_string(cflags.CompileOptions)); - for (auto& dep : pkg.Requires()) { + for (auto const& dep : pkg.Requires()) { auto it = providers.find(dep.Name); if (it != providers.end()) { tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", it->second); @@ -838,27 +894,28 @@ cmTarget* CreateCMakeTarget(std::string const& name, std::string const& prefix, } bool CheckPackageDependencies( - std::string const& name, std::string const& prefix, cmPkgConfigResult& pkg, - pkgStack& inStack, - std::unordered_map& outStack, - pkgProviders& providers, ImportEnv& imEnv) + std::string const& name, std::string const& prefix, + cmPkgConfigResolver const& pkg, pkgStack& inStack, + std::unordered_map const& outStack, + pkgProviders const& providers, ImportEnv imEnv, ImportState& state) { - for (auto& dep : pkg.Requires()) { + for (auto const& dep : pkg.Requires()) { auto prov_it = providers.find(dep.Name); if (prov_it != providers.end()) { continue; } - auto* tgt = imEnv.status.GetMakefile().FindTargetToUse( + auto* tgt = state.status.GetMakefile().FindTargetToUse( cmStrCat("@foreign_pkgcfg::", prefix, dep.Name), cm::TargetDomain::FOREIGN); if (tgt) { auto ver = tgt->GetProperty("VERSION"); - if (!cmPkgConfigResolver::CheckVersion(dep.VerReq, *ver)) { + if (!cmPkgConfigResolver::CheckVersion(dep, *ver)) { warn_or_error(cmStrCat("Package '", dep.Name, "' version '", *ver, "' does not meet version requirement '", - dep.VerReq.string(), "' of '", name, '\''), - imEnv); + cmPkgConfigResolver::VersionReqString(dep), + "' of '", name, '\''), + imEnv, state); return false; } continue; @@ -867,18 +924,18 @@ bool CheckPackageDependencies( auto it = outStack.find(dep.Name); if (it != outStack.end()) { auto ver = it->second.Version(); - if (!cmPkgConfigResolver::CheckVersion(dep.VerReq, ver)) { + if (!cmPkgConfigResolver::CheckVersion(dep, ver)) { warn_or_error(cmStrCat("Package '", dep.Name, "' version '", ver, "' does not meet version requirement '", - dep.VerReq.string(), "' of '", name, '\''), - imEnv); + cmPkgConfigResolver::VersionReqString(dep), + "' of '", name, '\''), + imEnv, state); return false; } continue; } - inStack[dep.Name].emplace_back( - pkgStackEntry{ std::move(dep.VerReq), name }); + inStack[dep.Name].emplace_back(pkgStackEntry{ dep, name }); } return true; @@ -910,11 +967,13 @@ std::pair PopulatePCTarget(PopulateArguments& args, return { !args.Required, false }; } auto& pcEnv = maybeEnv->first; - auto& imEnv = maybeEnv->second; pcEnv.AllowSysCflags = true; pcEnv.AllowSysLibs = true; + auto const& imEnv = maybeEnv->second; + ImportState state{ status, false }; + pkgProviders providers; if (args.Providers) { for (auto const& provider_str : *args.Providers) { @@ -923,7 +982,7 @@ std::pair PopulatePCTarget(PopulateArguments& args, providers.emplace(provider_str.substr(0, assignment), provider_str.substr(assignment + 1)); } else { - imEnv.status.SetError(cmStrCat( + state.status.SetError(cmStrCat( "No '=' found in BIND_PC_REQUIRES argument '", provider_str, '\'')); cmSystemTools::SetFatalErrorOccurred(); return { false, false }; @@ -932,33 +991,35 @@ std::pair PopulatePCTarget(PopulateArguments& args, } pkgStack inStack; - std::unordered_map outStack; + std::unordered_map outStack; - auto maybePackage = ImportPackage(*args.Package, args.Version, imEnv, pcEnv); + auto maybePackage = + ImportPackage(*args.Package, args.Version, imEnv, pcEnv, state); if (!maybePackage) { return { !args.Required, false }; } - imEnv.exact = false; + ImportEnv depEnv = imEnv; + depEnv.exact = false; if (!CheckPackageDependencies(*args.Package, prefix, *maybePackage, inStack, - outStack, providers, imEnv)) { + outStack, providers, depEnv, state)) { return { !args.Required, false }; } - outStack[*args.Package] = std::move(*maybePackage); + outStack.emplace(*args.Package, std::move(*maybePackage)); while (!inStack.empty()) { auto name = inStack.begin()->first; auto reqs = inStack.begin()->second; - maybePackage = ImportPackage(name, reqs, imEnv, pcEnv); + maybePackage = ImportPackage(name, reqs, depEnv, pcEnv, state); if (!maybePackage) { return { !args.Required, false }; } if (!CheckPackageDependencies(name, prefix, *maybePackage, inStack, - outStack, providers, imEnv)) { + outStack, providers, depEnv, state)) { return { !args.Required, false }; } inStack.erase(name); - outStack[std::move(name)] = std::move(*maybePackage); + outStack.emplace(std::move(name), std::move(*maybePackage)); } for (auto& entry : outStack) { diff --git a/Source/cmPkgConfigParser.cxx b/Source/cmPkgConfigParser.cxx index 3804527d1a..96d693db71 100644 --- a/Source/cmPkgConfigParser.cxx +++ b/Source/cmPkgConfigParser.cxx @@ -3,98 +3,248 @@ #include "cmPkgConfigParser.h" +#include +#include #include +#include +#include +#include +#include #include #include #include -cmPkgConfigValueElement::cmPkgConfigValueElement(bool isVariable, - cm::string_view data) - : IsVariable{ isVariable } - , Data{ data } +namespace { + +cmPkgConfigDependencyOperation LookupDependencyOperation(cm::string_view op) +{ + if (op == "<") { + return cmPkgConfigDependencyOperation::LessThan; + } + if (op == "<=") { + return cmPkgConfigDependencyOperation::LessThanEqual; + } + if (op == "=") { + return cmPkgConfigDependencyOperation::Equal; + } + if (op == "!=") { + return cmPkgConfigDependencyOperation::NotEqual; + } + if (op == ">") { + return cmPkgConfigDependencyOperation::GreaterThan; + } + if (op == ">=") { + return cmPkgConfigDependencyOperation::GreaterThanEqual; + } + return cmPkgConfigDependencyOperation::None; +} + +} // namespace + +cmPkgConfigValueItem::cmPkgConfigValueItem(std::string text, + bool isSubstitution) + : Text{ std::move(text) } + , IsSubstitution{ isSubstitution } { } -cmPkgConfigEntry::cmPkgConfigEntry(bool isVariable, cm::string_view key) - : IsVariable{ isVariable } - , Key{ key } +cmPkgConfigEntry::cmPkgConfigEntry(std::vector value, + std::string ident, + cmPkgConfigEntryKind kind) + : Value{ std::move(value) } + , Ident{ std::move(ident) } + , Kind{ kind } { } -cmPkgConfigParser::cmPkgConfigParser() +cmPkgConfigFileParser::cmPkgConfigFileParser() { - llpkgc_init(static_cast(this), &Settings_); + llpkgc_file_init(static_cast(this), &Settings_); } -llpkgc_errno_t cmPkgConfigParser::Parse(char* buf, std::size_t len) +cmPkgConfigFileParser::cmPkgConfigFileParser( + cmPkgConfigFileParser const& other) + : cmPkgConfigFileParser() { - return llpkgc_execute(static_cast(this), buf, len); + *this = other; } -llpkgc_errno_t cmPkgConfigParser::Finish() +cmPkgConfigFileParser::cmPkgConfigFileParser( + cmPkgConfigFileParser&& other) noexcept + : cmPkgConfigFileParser() { - return llpkgc_finish(static_cast(this)); + *this = std::move(other); } -llpkgc_errno_t cmPkgConfigParser::Finish(char* buf, std::size_t len) +cmPkgConfigFileParser& cmPkgConfigFileParser::operator=( + cmPkgConfigFileParser const& other) { - Parse(buf, len); - return llpkgc_finish(static_cast(this)); + if (this != &other) { + this->ResetCallbacks(); + llpkgc_file_init(static_cast(this), &Settings_); + Ptr_ = nullptr; + Len_ = 0; + Ident_ = other.Ident_; + Data_ = other.Data_; + } + return *this; } -int cmPkgConfigParser::OnSpanNext(char const*, std::size_t len) +cmPkgConfigFileParser& cmPkgConfigFileParser::operator=( + cmPkgConfigFileParser&& other) noexcept +{ + if (this != &other) { + this->ResetCallbacks(); + llpkgc_file_init(static_cast(this), &Settings_); + Ptr_ = nullptr; + Len_ = 0; + Ident_ = std::move(other.Ident_); + Data_ = std::move(other.Data_); + } + return *this; +} + +void cmPkgConfigFileParser::ResetCallbacks() +{ + Settings_.on_ident = OnIdentTr; + Settings_.on_value_literal = OnValueLiteralTr; + Settings_.on_value_substitution = OnValueSubstitutionTr; + Settings_.on_line_begin = nullptr; + Settings_.on_property_complete = OnPropertyCompleteTr; + Settings_.on_variable_complete = OnVariableCompleteTr; + Settings_.on_barewords_complete = OnBarewordsCompleteTr; + Settings_.on_value_literal_complete = OnValueLiteralCompleteTr; + Settings_.on_value_substitution_complete = OnValueSubstitutionCompleteTr; + Settings_.on_value_complete = OnValueCompleteTr; + Settings_.on_pkgc_complete = nullptr; +} + +llpkgc_file_errno_t cmPkgConfigFileParser::Parse(char const* input, + std::size_t len) +{ + return llpkgc_file_execute(static_cast(this), input, len); +} + +llpkgc_file_errno_t cmPkgConfigFileParser::Finish() +{ + return llpkgc_file_finish(static_cast(this)); +} + +void cmPkgConfigFileParser::Resume() +{ + llpkgc_file_resume(static_cast(this)); +} + +llpkgc_file_errno_t cmPkgConfigFileParser::GetErrno() const +{ + return llpkgc_file_get_errno(static_cast(this)); +} + +char const* cmPkgConfigFileParser::ErrorReason() const +{ + return llpkgc_file_get_error_reason(static_cast(this)); +} + +std::ptrdiff_t cmPkgConfigFileParser::ErrorOffset(char const* base) const +{ + char const* pos = + llpkgc_file_get_error_pos(static_cast(this)); + if (pos == nullptr) { + return -1; + } + return pos - base; +} + +int cmPkgConfigFileParser::OnIdentNext(char const* at, std::size_t len) +{ + Ident_.append(at, len); + return 0; +} + +int cmPkgConfigFileParser::OnIdentNextTr(llpkgc_file_t* parser, char const* at, + std::size_t len) +{ + return static_cast(parser)->OnIdentNext(at, len); +} + +void cmPkgConfigFileParser::AppendValue(std::string text, bool isSubstitution) +{ + if (!isSubstitution && !Data_.empty() && !Data_.back().Value.empty() && + !Data_.back().Value.back().IsSubstitution) { + Data_.back().Value.back().Text += text; + return; + } + + Data_.back().Value.emplace_back(std::move(text), isSubstitution); +} + +int cmPkgConfigFileParser::OnSpanNext(char const*, std::size_t len) { Len_ += len; return 0; } -int cmPkgConfigParser::OnSpanNextTr(llpkgc_t* parser, char const* at, - std::size_t len) +int cmPkgConfigFileParser::OnSpanNextTr(llpkgc_file_t* parser, char const* at, + std::size_t len) { - return static_cast(parser)->OnSpanNext(at, len); + return static_cast(parser)->OnSpanNext(at, len); } -int cmPkgConfigParser::OnKey(char const* at, std::size_t len) +int cmPkgConfigFileParser::OnIdent(char const* at, std::size_t len) { - Ptr_ = at; - Len_ = len; - Settings_.on_key = OnSpanNextTr; + Ident_.assign(at, len); + Settings_.on_ident = OnIdentNextTr; return 0; } -int cmPkgConfigParser::OnKeyTr(llpkgc_t* parser, char const* at, - std::size_t len) +int cmPkgConfigFileParser::OnIdentTr(llpkgc_file_t* parser, char const* at, + std::size_t len) { - return static_cast(parser)->OnKey(at, len); + return static_cast(parser)->OnIdent(at, len); } -int cmPkgConfigParser::OnKeywordComplete() +int cmPkgConfigFileParser::OnPropertyComplete() { - Data_.emplace_back(false, cm::string_view{ Ptr_, Len_ }); - Settings_.on_key = OnKeyTr; + Data_.emplace_back(std::vector{}, Ident_, + cmPkgConfigEntryKind::Property); + Settings_.on_ident = OnIdentTr; return 0; } -int cmPkgConfigParser::OnKeywordCompleteTr(llpkgc_t* parser) +int cmPkgConfigFileParser::OnPropertyCompleteTr(llpkgc_file_t* parser) { - return static_cast(parser)->OnKeywordComplete(); + return static_cast(parser)->OnPropertyComplete(); } -int cmPkgConfigParser::OnVariableComplete() +int cmPkgConfigFileParser::OnVariableComplete() { - Data_.emplace_back(true, cm::string_view{ Ptr_, Len_ }); - Settings_.on_key = OnKeyTr; + Data_.emplace_back(std::vector{}, Ident_, + cmPkgConfigEntryKind::Variable); + Settings_.on_ident = OnIdentTr; return 0; } -int cmPkgConfigParser::OnVariableCompleteTr(llpkgc_t* parser) +int cmPkgConfigFileParser::OnVariableCompleteTr(llpkgc_file_t* parser) { - return static_cast(parser)->OnVariableComplete(); + return static_cast(parser)->OnVariableComplete(); } -int cmPkgConfigParser::OnValueLiteral(char const* at, std::size_t len) +int cmPkgConfigFileParser::OnBarewordsComplete() +{ + Data_.emplace_back(std::vector{}, Ident_, + cmPkgConfigEntryKind::Barewords); + Settings_.on_ident = OnIdentTr; + return 0; +} + +int cmPkgConfigFileParser::OnBarewordsCompleteTr(llpkgc_file_t* parser) +{ + return static_cast(parser)->OnBarewordsComplete(); +} + +int cmPkgConfigFileParser::OnValueLiteral(char const* at, std::size_t len) { Ptr_ = at; Len_ = len; @@ -102,50 +252,408 @@ int cmPkgConfigParser::OnValueLiteral(char const* at, std::size_t len) return 0; } -int cmPkgConfigParser::OnValueLiteralTr(llpkgc_t* parser, char const* at, - std::size_t len) +int cmPkgConfigFileParser::OnValueLiteralTr(llpkgc_file_t* parser, + char const* at, std::size_t len) { - return static_cast(parser)->OnValueLiteral(at, len); + return static_cast(parser)->OnValueLiteral(at, len); } -int cmPkgConfigParser::OnValueLiteralComplete() +int cmPkgConfigFileParser::OnValueLiteralComplete() { Settings_.on_value_literal = OnValueLiteralTr; - - if (Len_) { - Data_.back().Val.emplace_back(false, cm::string_view{ Ptr_, Len_ }); - } - + AppendValue(std::string{ Ptr_, Len_ }, false); return 0; } -int cmPkgConfigParser::OnValueLiteralCompleteTr(llpkgc_t* parser) +int cmPkgConfigFileParser::OnValueLiteralCompleteTr(llpkgc_file_t* parser) { - return static_cast(parser)->OnValueLiteralComplete(); + return static_cast(parser)->OnValueLiteralComplete(); } -int cmPkgConfigParser::OnValueVariable(char const* at, std::size_t len) +int cmPkgConfigFileParser::OnValueSubstitution(char const* at, std::size_t len) { Ptr_ = at; Len_ = len; - Settings_.on_value_variable = OnSpanNextTr; + Settings_.on_value_substitution = OnSpanNextTr; return 0; } -int cmPkgConfigParser::OnValueVariableTr(llpkgc_t* parser, char const* at, - std::size_t len) +int cmPkgConfigFileParser::OnValueSubstitutionTr(llpkgc_file_t* parser, + char const* at, + std::size_t len) { - return static_cast(parser)->OnValueVariable(at, len); + return static_cast(parser)->OnValueSubstitution(at, + len); } -int cmPkgConfigParser::OnValueVariableComplete() +int cmPkgConfigFileParser::OnValueSubstitutionComplete() { - Settings_.on_value_variable = OnValueVariableTr; - Data_.back().Val.emplace_back(true, cm::string_view{ Ptr_, Len_ }); + Settings_.on_value_substitution = OnValueSubstitutionTr; + AppendValue(std::string{ Ptr_, Len_ }, true); return 0; } -int cmPkgConfigParser::OnValueVariableCompleteTr(llpkgc_t* parser) +int cmPkgConfigFileParser::OnValueSubstitutionCompleteTr(llpkgc_file_t* parser) { - return static_cast(parser)->OnValueVariableComplete(); + return static_cast(parser) + ->OnValueSubstitutionComplete(); +} + +int cmPkgConfigFileParser::OnValueComplete() +{ + return 0; +} + +int cmPkgConfigFileParser::OnValueCompleteTr(llpkgc_file_t* parser) +{ + return static_cast(parser)->OnValueComplete(); +} + +cmPkgConfigFragmentParser::cmPkgConfigFragmentParser() +{ + llpkgc_frag_init(static_cast(this), &Settings_); +} + +llpkgc_frag_errno_t cmPkgConfigFragmentParser::Parse(char const* input, + std::size_t len) +{ + return llpkgc_frag_execute(static_cast(this), input, len); +} + +llpkgc_frag_errno_t cmPkgConfigFragmentParser::Finish() +{ + return llpkgc_frag_finish(static_cast(this)); +} + +llpkgc_frag_errno_t cmPkgConfigFragmentParser::GetErrno() const +{ + return llpkgc_frag_get_errno(static_cast(this)); +} + +char const* cmPkgConfigFragmentParser::ErrorReason() const +{ + return llpkgc_frag_get_error_reason(static_cast(this)); +} + +int cmPkgConfigFragmentParser::OnFragmentText(char const* at, std::size_t len) +{ + CurrentFragment_.append(at, len); + return 0; +} + +int cmPkgConfigFragmentParser::OnFragmentTextTr(llpkgc_frag_t* parser, + char const* at, + std::size_t len) +{ + return static_cast(parser)->OnFragmentText(at, + len); +} + +int cmPkgConfigFragmentParser::OnFragmentComplete() +{ + Fragments_.push_back(std::move(CurrentFragment_)); + CurrentFragment_.clear(); + return 0; +} + +int cmPkgConfigFragmentParser::OnFragmentCompleteTr(llpkgc_frag_t* parser) +{ + return static_cast(parser)->OnFragmentComplete(); +} + +cmPkgConfigDependencyParser::cmPkgConfigDependencyParser() +{ + llpkgc_dep_init(static_cast(this), &Settings_); +} + +llpkgc_dep_errno_t cmPkgConfigDependencyParser::Parse(char const* input, + std::size_t len) +{ + return llpkgc_dep_execute(static_cast(this), input, len); +} + +llpkgc_dep_errno_t cmPkgConfigDependencyParser::Finish() +{ + return llpkgc_dep_finish(static_cast(this)); +} + +llpkgc_dep_errno_t cmPkgConfigDependencyParser::GetErrno() const +{ + return llpkgc_dep_get_errno(static_cast(this)); +} + +char const* cmPkgConfigDependencyParser::ErrorReason() const +{ + return llpkgc_dep_get_error_reason(static_cast(this)); +} + +int cmPkgConfigDependencyParser::OnDependencyName(char const* at, + std::size_t len) +{ + Current_.Name.assign(at, len); + return 0; +} + +int cmPkgConfigDependencyParser::OnDependencyNameTr(llpkgc_dep_t* parser, + char const* at, + std::size_t len) +{ + return static_cast(parser)->OnDependencyName( + at, len); +} + +int cmPkgConfigDependencyParser::OnDependencyOperator(char const* at, + std::size_t len) +{ + Current_.Operation = LookupDependencyOperation(cm::string_view{ at, len }); + return 0; +} + +int cmPkgConfigDependencyParser::OnDependencyOperatorTr(llpkgc_dep_t* parser, + char const* at, + std::size_t len) +{ + return static_cast(parser) + ->OnDependencyOperator(at, len); +} + +int cmPkgConfigDependencyParser::OnDependencyVersion(char const* at, + std::size_t len) +{ + Current_.Version.assign(at, len); + return 0; +} + +int cmPkgConfigDependencyParser::OnDependencyVersionTr(llpkgc_dep_t* parser, + char const* at, + std::size_t len) +{ + return static_cast(parser) + ->OnDependencyVersion(at, len); +} + +int cmPkgConfigDependencyParser::OnDependencyComplete() +{ + Dependencies_.push_back(std::move(Current_)); + Current_ = cmPkgConfigDependencySpec{}; + return 0; +} + +int cmPkgConfigDependencyParser::OnDependencyCompleteTr(llpkgc_dep_t* parser) +{ + return static_cast(parser) + ->OnDependencyComplete(); +} + +llpkgc_file_errno_t cmPkgConfigParser::Parse(char const* input, + std::size_t len) +{ + return File_.Parse(input, len); +} + +llpkgc_file_errno_t cmPkgConfigParser::Finish() +{ + return File_.Finish(); +} + +llpkgc_file_errno_t cmPkgConfigParser::Finish(char const* input, + std::size_t len) +{ + auto err = Parse(input, len); + if (err != PFCE_OK) { + return err; + } + return Finish(); +} + +llpkgc_file_errno_t cmPkgConfigParser::GetErrno() const +{ + return File_.GetErrno(); +} + +char const* cmPkgConfigParser::ErrorReason() const +{ + return File_.ErrorReason(); +} + +std::ptrdiff_t cmPkgConfigParser::ErrorOffset(char const* base) const +{ + return File_.ErrorOffset(base); +} + +void cmPkgConfigParser::SetVariable(std::string name, std::string value) +{ + ValueItems variable; + variable.emplace_back(std::move(value), false); + StoredVariables_[std::move(name)] = std::move(variable); + Cache_.clear(); +} + +std::string cmPkgConfigParser::GetVariable(std::string const& name) +{ + return ResolveValue(StoredVariables_[name]); +} + +bool cmPkgConfigParser::HasProperty(std::string const& name) const +{ + return std::any_of(StoredProperties_.begin(), StoredProperties_.end(), + [&name](cmPkgConfigEntry const& entry) -> bool { + return entry.Ident == name; + }); +} + +std::vector cmPkgConfigParser::GetFragments( + std::string const& name) +{ + auto it = Cache_.find(name); + if (it != Cache_.end()) { + return it->second.Fragments; + } + + cmPkgConfigResolvedValue cache; + if (!ResolveAndParse(name, cache)) { + return {}; + } + return cache.Fragments; +} + +std::vector cmPkgConfigParser::GetDependencies( + std::string const& name) +{ + auto it = Cache_.find(name); + if (it != Cache_.end()) { + return it->second.Dependencies; + } + + cmPkgConfigResolvedValue cache; + if (!ResolveAndParse(name, cache)) { + return {}; + } + return cache.Dependencies; +} + +std::string cmPkgConfigParser::GetLiteral(std::string const& name) +{ + auto it = Cache_.find(name); + if (it != Cache_.end()) { + return it->second.Literal; + } + + cmPkgConfigResolvedValue cache; + if (!ResolveAndParse(name, cache)) { + return {}; + } + return cache.Literal; +} + +void cmPkgConfigParser::ParseComplete() +{ + StoredVariables_.clear(); + StoredProperties_.clear(); + Cache_.clear(); + + for (auto const& entry : File_.Data_) { + switch (entry.Kind) { + case cmPkgConfigEntryKind::Variable: + StoredVariables_[entry.Ident] = entry.Value; + break; + case cmPkgConfigEntryKind::Property: + StoredProperties_.push_back(entry); + break; + default: + break; + } + } +} + +std::string cmPkgConfigParser::ResolveValue(ValueItems const& items) +{ + return ResolveValueImpl(items, {}); +} + +std::string cmPkgConfigParser::ResolveValueImpl( + ValueItems const& items, std::set resolving) +{ + std::string result; + if (items.empty()) { + return result; + } + + for (auto const& item : items) { + if (item.IsSubstitution) { + if (resolving.count(item.Text)) { + result += "${"; + result += item.Text; + result += "}"; + continue; + } + auto it = StoredVariables_.find(item.Text); + if (it != StoredVariables_.end()) { + resolving.insert(item.Text); + result += ResolveValueImpl(it->second, resolving); + } + } else { + result += item.Text; + } + } + + return result; +} + +bool cmPkgConfigParser::ResolveAndParse(std::string const& name, + cmPkgConfigResolvedValue& cache) +{ + cmPkgConfigEntry const* entry = nullptr; + for (auto const& candidate : StoredProperties_) { + if (candidate.Ident == name) { + entry = &candidate; + break; + } + } + if (entry == nullptr) { + return false; + } + + std::string resolved = ResolveValue(entry->Value); + auto start = resolved.find_first_not_of(" \t"); + if (start != std::string::npos) { + resolved.erase(0, start); + } + cache.Literal = resolved; + + std::string lower = name; + for (auto& c : lower) { + c = static_cast(std::tolower(static_cast(c))); + } + + if (IsFragmentProperty(lower)) { + cmPkgConfigFragmentParser fragmentParser; + auto err = fragmentParser.Parse(resolved.data(), resolved.size()); + if (err == PFFE_OK && fragmentParser.Finish() == PFFE_OK) { + cache.Fragments = fragmentParser.Fragments(); + } + } else if (IsDependencyProperty(lower)) { + cmPkgConfigDependencyParser dependencyParser; + auto err = dependencyParser.Parse(resolved.data(), resolved.size()); + if (err == PDEP_OK && dependencyParser.Finish() == PDEP_OK) { + cache.Dependencies = dependencyParser.Dependencies(); + } + } + + Cache_[name] = cache; + return true; +} + +bool cmPkgConfigParser::IsFragmentProperty(std::string const& lower) +{ + return lower == "cflags" || lower == "cflags.private" || + lower == "cflags.shared" || lower == "libs" || lower == "libs.private" || + lower == "libs.shared"; +} + +bool cmPkgConfigParser::IsDependencyProperty(std::string const& lower) +{ + return lower == "conflicts" || lower == "provides" || lower == "requires" || + lower == "requires.private" || lower == "requires.internal" || + lower == "requires.shared"; } diff --git a/Source/cmPkgConfigParser.h b/Source/cmPkgConfigParser.h index 9500c115de..662b1b72f1 100644 --- a/Source/cmPkgConfigParser.h +++ b/Source/cmPkgConfigParser.h @@ -6,88 +6,259 @@ #include "cmConfigure.h" // IWYU pragma: keep #include +#include +#include +#include #include -#include - #include -struct cmPkgConfigValueElement +struct cmPkgConfigValueItem { + cmPkgConfigValueItem() = default; + cmPkgConfigValueItem(std::string text, bool isSubstitution); - cmPkgConfigValueElement() = default; + std::string Text; + bool IsSubstitution = false; +}; - cmPkgConfigValueElement(bool isVariable, cm::string_view data); - - bool IsVariable; - cm::string_view Data; +enum class cmPkgConfigEntryKind +{ + Property, + Variable, + Barewords, }; struct cmPkgConfigEntry { - cmPkgConfigEntry() = default; + cmPkgConfigEntry(std::vector value, std::string ident, + cmPkgConfigEntryKind kind); - cmPkgConfigEntry(bool isVariable, cm::string_view key); - - bool IsVariable; - cm::string_view Key; - std::vector Val; + std::vector Value; + std::string Ident; + cmPkgConfigEntryKind Kind = cmPkgConfigEntryKind::Barewords; }; -class cmPkgConfigParser : llpkgc_t +class cmPkgConfigFileParser : private llpkgc_file_t { + friend class cmPkgConfigParser; + public: - cmPkgConfigParser(); + cmPkgConfigFileParser(); + cmPkgConfigFileParser(cmPkgConfigFileParser const& other); + cmPkgConfigFileParser(cmPkgConfigFileParser&& other) noexcept; + cmPkgConfigFileParser& operator=(cmPkgConfigFileParser const& other); + cmPkgConfigFileParser& operator=(cmPkgConfigFileParser&& other) noexcept; - llpkgc_errno_t Parse(char* buf, std::size_t len); + llpkgc_file_errno_t Parse(char const* input, std::size_t len); + llpkgc_file_errno_t Finish(); + void Resume(); - llpkgc_errno_t Finish(); - llpkgc_errno_t Finish(char* buf, std::size_t len); - - std::vector& Data() { return Data_; } + llpkgc_file_errno_t GetErrno() const; + char const* ErrorReason() const; + std::ptrdiff_t ErrorOffset(char const* base) const; private: + void ResetCallbacks(); + + int OnIdentNext(char const* at, std::size_t len); + static int OnIdentNextTr(llpkgc_file_t* parser, char const* at, + std::size_t len); + + void AppendValue(std::string text, bool isSubstitution); + int OnSpanNext(char const*, std::size_t len); - static int OnSpanNextTr(llpkgc_t* parser, char const* at, std::size_t len); + static int OnSpanNextTr(llpkgc_file_t* parser, char const* at, + std::size_t len); - int OnKey(char const* at, std::size_t len); - static int OnKeyTr(llpkgc_t* parser, char const* at, std::size_t len); + int OnIdent(char const* at, std::size_t len); + static int OnIdentTr(llpkgc_file_t* parser, char const* at, std::size_t len); - int OnKeywordComplete(); - static int OnKeywordCompleteTr(llpkgc_t* parser); + int OnPropertyComplete(); + static int OnPropertyCompleteTr(llpkgc_file_t* parser); int OnVariableComplete(); - static int OnVariableCompleteTr(llpkgc_t* parser); + static int OnVariableCompleteTr(llpkgc_file_t* parser); + + int OnBarewordsComplete(); + static int OnBarewordsCompleteTr(llpkgc_file_t* parser); int OnValueLiteral(char const* at, std::size_t len); - static int OnValueLiteralTr(llpkgc_t* parser, char const* at, + static int OnValueLiteralTr(llpkgc_file_t* parser, char const* at, std::size_t len); int OnValueLiteralComplete(); - static int OnValueLiteralCompleteTr(llpkgc_t* parser); + static int OnValueLiteralCompleteTr(llpkgc_file_t* parser); - int OnValueVariable(char const* at, std::size_t len); - static int OnValueVariableTr(llpkgc_t* parser, char const* at, - std::size_t len); + int OnValueSubstitution(char const* at, std::size_t len); + static int OnValueSubstitutionTr(llpkgc_file_t* parser, char const* at, + std::size_t len); - int OnValueVariableComplete(); - static int OnValueVariableCompleteTr(llpkgc_t* parser); + int OnValueSubstitutionComplete(); + static int OnValueSubstitutionCompleteTr(llpkgc_file_t* parser); - llpkgc_settings_t Settings_{ - OnKeyTr, + int OnValueComplete(); + static int OnValueCompleteTr(llpkgc_file_t* parser); + + llpkgc_file_settings_t Settings_{ + OnIdentTr, OnValueLiteralTr, - OnValueVariableTr, + OnValueSubstitutionTr, nullptr, // on_line_begin - OnKeywordCompleteTr, + OnPropertyCompleteTr, OnVariableCompleteTr, + OnBarewordsCompleteTr, OnValueLiteralCompleteTr, - OnValueVariableCompleteTr, - nullptr, // on_value_complete + OnValueSubstitutionCompleteTr, + OnValueCompleteTr, nullptr, // on_pkgc_complete }; - char const* Ptr_; - std::size_t Len_; + char const* Ptr_ = nullptr; + std::size_t Len_ = 0; + std::string Ident_; std::vector Data_; }; + +class cmPkgConfigFragmentParser : private llpkgc_frag_t +{ +public: + cmPkgConfigFragmentParser(); + + llpkgc_frag_errno_t Parse(char const* input, std::size_t len); + llpkgc_frag_errno_t Finish(); + + llpkgc_frag_errno_t GetErrno() const; + char const* ErrorReason() const; + + std::vector const& Fragments() const { return Fragments_; } + +private: + int OnFragmentText(char const* at, std::size_t len); + static int OnFragmentTextTr(llpkgc_frag_t* parser, char const* at, + std::size_t len); + + int OnFragmentComplete(); + static int OnFragmentCompleteTr(llpkgc_frag_t* parser); + + llpkgc_frag_settings_t Settings_{ + OnFragmentTextTr, OnFragmentCompleteTr, + nullptr, // on_fraglist_complete + }; + + std::string CurrentFragment_; + std::vector Fragments_; +}; + +enum class cmPkgConfigDependencyOperation +{ + None, + LessThan, + LessThanEqual, + Equal, + NotEqual, + GreaterThan, + GreaterThanEqual, +}; + +struct cmPkgConfigDependencySpec +{ + std::string Name; + cmPkgConfigDependencyOperation Operation = + cmPkgConfigDependencyOperation::None; + std::string Version; +}; + +class cmPkgConfigDependencyParser : private llpkgc_dep_t +{ +public: + cmPkgConfigDependencyParser(); + + llpkgc_dep_errno_t Parse(char const* input, std::size_t len); + llpkgc_dep_errno_t Finish(); + + llpkgc_dep_errno_t GetErrno() const; + char const* ErrorReason() const; + + std::vector const& Dependencies() const + { + return Dependencies_; + } + +private: + int OnDependencyName(char const* at, std::size_t len); + static int OnDependencyNameTr(llpkgc_dep_t* parser, char const* at, + std::size_t len); + + int OnDependencyOperator(char const* at, std::size_t len); + static int OnDependencyOperatorTr(llpkgc_dep_t* parser, char const* at, + std::size_t len); + + int OnDependencyVersion(char const* at, std::size_t len); + static int OnDependencyVersionTr(llpkgc_dep_t* parser, char const* at, + std::size_t len); + + int OnDependencyComplete(); + static int OnDependencyCompleteTr(llpkgc_dep_t* parser); + + llpkgc_dep_settings_t Settings_{ + OnDependencyNameTr, + OnDependencyOperatorTr, + OnDependencyVersionTr, + OnDependencyCompleteTr, + nullptr, // on_dep_list_complete + }; + + cmPkgConfigDependencySpec Current_; + std::vector Dependencies_; +}; + +struct cmPkgConfigResolvedValue +{ + std::vector Fragments; + std::vector Dependencies; + std::string Literal; +}; + +class cmPkgConfigParser +{ +public: + cmPkgConfigParser() = default; + + llpkgc_file_errno_t Parse(char const* input, std::size_t len); + llpkgc_file_errno_t Finish(); + llpkgc_file_errno_t Finish(char const* data, std::size_t len); + + llpkgc_file_errno_t GetErrno() const; + char const* ErrorReason() const; + std::ptrdiff_t ErrorOffset(char const* base) const; + + void SetVariable(std::string name, std::string value); + std::string GetVariable(std::string const& name); + bool HasProperty(std::string const& name) const; + std::vector GetFragments(std::string const& name); + std::vector GetDependencies( + std::string const& name); + std::string GetLiteral(std::string const& name); + + void ParseComplete(); + +private: + using ValueItems = std::vector; + + std::string ResolveValue(ValueItems const& items); + std::string ResolveValueImpl(ValueItems const& items, + std::set resolving); + + bool ResolveAndParse(std::string const& name, + cmPkgConfigResolvedValue& cache); + + static bool IsFragmentProperty(std::string const& lower); + static bool IsDependencyProperty(std::string const& lower); + + cmPkgConfigFileParser File_; + std::unordered_map StoredVariables_; + std::vector StoredProperties_; + std::unordered_map Cache_; +}; diff --git a/Source/cmPkgConfigResolver.cxx b/Source/cmPkgConfigResolver.cxx index 4d7ef27930..b8054c8acb 100644 --- a/Source/cmPkgConfigResolver.cxx +++ b/Source/cmPkgConfigResolver.cxx @@ -7,214 +7,307 @@ #include #include #include -#include #include #include #include -#include #include "cmsys/String.h" +#include "cmOutputConverter.h" #include "cmPkgConfigParser.h" #include "cmStringAlgorithms.h" namespace { -void TrimBack(std::string& str) +bool HasPrefix(std::string const& value, char const* prefix) { - if (!str.empty()) { - auto it = str.end() - 1; - for (; cmsysString_isspace(*it); --it) { - if (it == str.begin()) { - str.clear(); - return; - } - } - str.erase(++it, str.end()); - } + std::size_t const len = std::strlen(prefix); + return value.size() >= len && value.compare(0, len, prefix) == 0; } -std::string AppendAndTrim(std::string& str, cm::string_view sv) +void AppendFlag(std::string& flagline, std::string const& flag) { - auto size = str.length(); - str += sv; - if (str.empty()) { - return {}; + if (!flagline.empty()) { + flagline += ' '; } - - auto begin = str.begin() + size; - auto cur = str.end() - 1; - - while (cur != begin && cmsysString_isspace(*cur)) { - --cur; - } - - if (cmsysString_isspace(*cur)) { - return {}; - } - - return { &*begin, static_cast(cur - begin) + 1 }; + flagline += flag; } -cm::string_view TrimFlag(cm::string_view flag) +bool IsSystemPath(std::string const& path, + std::vector const& systemPaths) { - std::size_t trim_size = 2; - for (auto c = flag.rbegin(); c != flag.rend() && cmsysString_isspace(*c); - ++c) { - ++trim_size; + return std::find(systemPaths.begin(), systemPaths.end(), path) != + systemPaths.end(); +} + +std::vector DefaultSystemIncludePaths() +{ + return std::vector{ "/usr/include" }; +} + +std::vector DefaultSystemLibraryPaths() +{ + return std::vector{ "/usr/lib" }; +} + +std::vector const& SystemCflags(cmPkgConfigEnv const& env, + std::vector& def) +{ + if (env.SysCflags) { + return *env.SysCflags; } - return { flag.data() + 2, flag.size() - trim_size }; + def = DefaultSystemIncludePaths(); + return def; +} + +std::vector const& SystemLibs(cmPkgConfigEnv const& env, + std::vector& def) +{ + if (env.SysLibs) { + return *env.SysLibs; + } + def = DefaultSystemLibraryPaths(); + return def; } } // namespace -std::string cmPkgConfigVersionReq::string() const +std::string cmPkgConfigResolver::VersionReqString( + cmPkgConfigDependencySpec const& spec) { - switch (Operation) { - case ANY: + switch (spec.Operation) { + case cmPkgConfigDependencyOperation::None: return ""; - case LT: - return cmStrCat('<', Version); - case LT_EQ: - return cmStrCat("<=", Version); - case EQ: - return cmStrCat('=', Version); - case NEQ: - return cmStrCat("!=", Version); - case GT_EQ: - return cmStrCat(">=", Version); - case GT: - return cmStrCat('>', Version); + 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 ""; } -std::string cmPkgConfigResult::StrOrDefault(std::string const& key, - cm::string_view def) +cmPkgConfigResolver::cmPkgConfigResolver(cmPkgConfigParser parser, + cmPkgConfigEnv const& env, + std::string const& pcFileDir) + : Parser{ std::move(parser) } + , Env{ &env } { - auto it = Keywords.find(key); - return it == Keywords.end() ? std::string{ def } : it->second; -}; - -std::string cmPkgConfigResult::Name() -{ - return StrOrDefault("Name"); + this->Parser.ParseComplete(); + this->ConfigureParser(pcFileDir); } -std::string cmPkgConfigResult::Description() +cm::optional cmPkgConfigResolver::Resolve( + cmPkgConfigParser parser, cmPkgConfigEnv const& env, Strictness strictness, + std::string const& pcFileDir) { - return StrOrDefault("Description"); -} - -std::string cmPkgConfigResult::Version() -{ - return StrOrDefault("Version"); -} - -std::vector cmPkgConfigResult::Conflicts() -{ - auto it = Keywords.find("Conflicts"); - if (it == Keywords.end()) { + cmPkgConfigResolver resolver{ std::move(parser), env, pcFileDir }; + if (strictness != Strictness::BestEffort && !resolver.HasRequiredFields()) { return {}; } - - return cmPkgConfigResolver::ParseDependencies(it->second); -} - -std::vector cmPkgConfigResult::Provides() -{ - auto it = Keywords.find("Provides"); - if (it == Keywords.end()) { + if (strictness == Strictness::Strict && resolver.HasStrictConflicts()) { return {}; } - - return cmPkgConfigResolver::ParseDependencies(it->second); + return resolver; } -std::vector cmPkgConfigResult::Requires(bool priv) +cm::optional cmPkgConfigResolver::ResolveStrict( + cmPkgConfigParser parser, cmPkgConfigEnv const& env, + std::string const& pcFileDir) { - auto it = Keywords.find(priv ? "Requires.private" : "Requires"); - if (it == Keywords.end()) { - return {}; - } - - return cmPkgConfigResolver::ParseDependencies(it->second); + return Resolve(std::move(parser), env, Strictness::Strict, pcFileDir); } -cmPkgConfigCflagsResult cmPkgConfigResult::Cflags(bool priv) +cm::optional cmPkgConfigResolver::ResolvePermissive( + cmPkgConfigParser parser, cmPkgConfigEnv const& env, + std::string const& pcFileDir) { - 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{ "/usr/include" }); - } - - return cmPkgConfigResolver::MangleCflags( - tokens, std::vector{ "/usr/include" }); + return Resolve(std::move(parser), env, Strictness::Permissive, pcFileDir); } -cmPkgConfigLibsResult cmPkgConfigResult::Libs(bool priv) +cmPkgConfigResolver cmPkgConfigResolver::ResolveBestEffort( + cmPkgConfigParser parser, cmPkgConfigEnv const& env, + std::string const& pcFileDir) { - auto it = Keywords.find(priv ? "Libs.private" : "Libs"); - if (it == Keywords.end()) { - return cmPkgConfigLibsResult(); + 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", "/"); } - auto tokens = cmPkgConfigResolver::TokenizeFlags(it->second); + if (this->Env->TopBuildDir) { + this->Parser.SetVariable( + "pc_top_builddir", + cmOutputConverter::EscapeForShell(*this->Env->TopBuildDir, + cmOutputConverter::Shell_Flag_IsUnix)); + } - if (env->AllowSysLibs) { - if (env->SysrootDir) { - return cmPkgConfigResolver::MangleLibs(tokens, *env->SysrootDir); + 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 cmPkgConfigResolver::Fragments( + std::string const& key) const +{ + return this->Parser.GetFragments(key); +} + +std::vector cmPkgConfigResolver::Dependencies( + std::string const& key) const +{ + return this->Parser.GetDependencies(key); +} + +std::vector cmPkgConfigResolver::Conflicts() const +{ + return this->Dependencies("Conflicts"); +} + +std::vector cmPkgConfigResolver::Provides() const +{ + return this->Dependencies("Provides"); +} + +std::vector 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 const& flags, cmPkgConfigEnv const& env) +{ + cmPkgConfigCflagsResult result; + std::vector 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); } - return cmPkgConfigResolver::MangleLibs(tokens); - } - if (env->SysLibs) { - if (env->SysrootDir) { - return cmPkgConfigResolver::MangleLibs(tokens, *env->SysrootDir, - *env->SysLibs); + 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 cmPkgConfigResolver::MangleLibs(tokens, *env->SysLibs); } - if (env->SysrootDir) { - return cmPkgConfigResolver::MangleLibs( - tokens, *env->SysrootDir, std::vector{ "/usr/lib" }); + return result; +} + +cmPkgConfigLibsResult cmPkgConfigResolver::MangleLibs( + std::vector const& flags, cmPkgConfigEnv const& env) +{ + cmPkgConfigLibsResult result; + std::vector 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 cmPkgConfigResolver::MangleLibs( - tokens, std::vector{ "/usr/lib" }); + 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) @@ -222,538 +315,30 @@ void cmPkgConfigResolver::ReplaceSep(std::string& list) #ifndef _WIN32 std::replace(list.begin(), list.end(), ':', ';'); #else - static_cast(list); // Unused parameter + static_cast(list); #endif } -cm::optional cmPkgConfigResolver::ResolveStrict( - std::vector const& entries, cmPkgConfigEnv& env) +bool cmPkgConfigResolver::CheckVersion( + cmPkgConfigDependencySpec const& desired, std::string const& provided) { - cm::optional 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 = &env; - - for (auto const& 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 cmPkgConfigResolver::ResolvePermissive( - std::vector const& entries, cmPkgConfigEnv& env) -{ - cm::optional result; - - cmPkgConfigResult config = ResolveBestEffort(entries, env); - auto const& 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( - std::vector const& 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 = &env; - - for (auto const& 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( - cmPkgConfigEntry const& entry, - std::unordered_map const& variables) -{ - std::string result; - for (auto const& 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 cmPkgConfigResolver::HandleVariableStrict( - cmPkgConfigEntry const& entry, - std::unordered_map const& variables) -{ - cm::optional result; - - std::string value; - for (auto const& 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( - cmPkgConfigEntry const& entry, - std::unordered_map const& variables) -{ - std::string result; - for (auto const& 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 cmPkgConfigResolver::TokenizeFlags( - std::string const& flagline) -{ - std::vector result; - - auto it = flagline.begin(); - while (it != flagline.end() && cmsysString_isspace(*it)) { - ++it; - } - - while (it != flagline.end()) { - char const* start = &(*it); - std::size_t len = 0; - - for (; it != flagline.end() && !cmsysString_isspace(*it); ++it) { - ++len; - } - - for (; it != flagline.end() && cmsysString_isspace(*it); ++it) { - ++len; - } - - result.emplace_back(start, len); - } - - return result; -} - -cmPkgConfigCflagsResult cmPkgConfigResolver::MangleCflags( - std::vector const& 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( - std::vector const& flags, std::string const& 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( - std::vector const& flags, - std::vector const& syspaths) -{ - cmPkgConfigCflagsResult result; - - for (auto flag : flags) { - if (flag.rfind("-I", 0) == 0) { - cm::string_view trimmed = TrimFlag(flag); - if (std::all_of( - syspaths.begin(), syspaths.end(), - [&](std::string const& path) { return path != trimmed; })) { - result.Includes.emplace_back(AppendAndTrim(result.Flagline, flag)); - } - - } else { - result.CompileOptions.emplace_back(AppendAndTrim(result.Flagline, flag)); - } - } - - return result; -} - -cmPkgConfigCflagsResult cmPkgConfigResolver::MangleCflags( - std::vector const& flags, std::string const& sysroot, - std::vector const& syspaths) -{ - cmPkgConfigCflagsResult result; - - for (auto flag : flags) { - if (flag.rfind("-I", 0) == 0) { - std::string reroot = Reroot(flag, "-I", sysroot); - cm::string_view trimmed = TrimFlag(reroot); - if (std::all_of( - syspaths.begin(), syspaths.end(), - [&](std::string const& path) { return path != trimmed; })) { - result.Includes.emplace_back(AppendAndTrim(result.Flagline, reroot)); - } - - } else { - result.CompileOptions.emplace_back(AppendAndTrim(result.Flagline, flag)); - } - } - - return result; -} - -cmPkgConfigLibsResult cmPkgConfigResolver::MangleLibs( - std::vector const& 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( - std::vector const& flags, std::string const& 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( - std::vector const& flags, - std::vector const& syspaths) -{ - cmPkgConfigLibsResult result; - - for (auto flag : flags) { - if (flag.rfind("-L", 0) == 0) { - cm::string_view trimmed = TrimFlag(flag); - if (std::all_of( - syspaths.begin(), syspaths.end(), - [&](std::string const& path) { return path != trimmed; })) { - 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( - std::vector const& flags, std::string const& sysroot, - std::vector const& syspaths) -{ - cmPkgConfigLibsResult result; - - for (auto flag : flags) { - if (flag.rfind("-L", 0) == 0) { - std::string reroot = Reroot(flag, "-L", sysroot); - cm::string_view trimmed = TrimFlag(reroot); - if (std::all_of( - syspaths.begin(), syspaths.end(), - [&](std::string const& path) { return path != trimmed; })) { - 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, - std::string const& 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 (!cmsysString_isspace(*cur)) { - break; - } - } - - for (; cur != end && !cmsysString_isspace(*cur) && *cur != ','; ++cur) { - result.Version += *cur; - } - - return result; -} - -std::vector cmPkgConfigResolver::ParseDependencies( - std::string const& deps) -{ - - std::vector result; - - auto cur = deps.begin(); - auto end = deps.end(); - - while (cur != end) { - while ((cmsysString_isspace(*cur) || *cur == ',')) { - if (++cur == end) { - return result; - } - } - - result.emplace_back(); - auto& dep = result.back(); - - while (!cmsysString_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 (!cmsysString_isspace(*cur)) { - return false; - } - } - }; - - if (!in_operator()) { - continue; - } - - dep.VerReq = ParseVersion(cur, end); - } - - return result; -} - -bool cmPkgConfigResolver::CheckVersion(cmPkgConfigVersionReq const& desired, - std::string const& provided) -{ - - if (desired.Operation == cmPkgConfigVersionReq::ANY) { + if (desired.Operation == cmPkgConfigDependencyOperation::None) { 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: + case cmPkgConfigDependencyOperation::Equal: return comp == 0; - case cmPkgConfigVersionReq::NEQ: + case cmPkgConfigDependencyOperation::NotEqual: return comp != 0; - case cmPkgConfigVersionReq::GT: + case cmPkgConfigDependencyOperation::GreaterThan: return comp < 0; - case cmPkgConfigVersionReq::GT_EQ: + case cmPkgConfigDependencyOperation::GreaterThanEqual: return comp <= 0; - case cmPkgConfigVersionReq::LT: + case cmPkgConfigDependencyOperation::LessThan: return comp > 0; - case cmPkgConfigVersionReq::LT_EQ: + case cmPkgConfigDependencyOperation::LessThanEqual: return comp >= 0; default: return true; @@ -870,26 +455,3 @@ bool cmPkgConfigResolver::CheckVersion(cmPkgConfigVersionReq const& desired, return check_with_op(1); } - -cmPkgConfigVersionReq cmPkgConfigResolver::ParseVersion( - std::string const& 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; -} diff --git a/Source/cmPkgConfigResolver.h b/Source/cmPkgConfigResolver.h index 992429a3e5..d54c008e2c 100644 --- a/Source/cmPkgConfigResolver.h +++ b/Source/cmPkgConfigResolver.h @@ -6,14 +6,11 @@ #include "cmConfigure.h" // IWYU pragma: keep #include -#include #include #include -#include -// From cmPkgConfigParser.h, IWYU doesn't like including the header -struct cmPkgConfigEntry; +#include "cmPkgConfigParser.h" struct cmPkgConfigCflagsResult { @@ -30,29 +27,6 @@ struct cmPkgConfigLibsResult std::vector LinkOptions; }; -struct cmPkgConfigVersionReq -{ - enum - { - ANY = 0, - LT, - LT_EQ, - EQ, - NEQ, - GT_EQ, - GT, - } Operation = ANY; - std::string Version; - - std::string string() const; -}; - -struct cmPkgConfigDependency -{ - std::string Name; - cmPkgConfigVersionReq VerReq; -}; - struct cmPkgConfigEnv { cm::optional> Path; @@ -70,46 +44,51 @@ struct cmPkgConfigEnv bool AllowSysLibs = true; }; -class cmPkgConfigResult -{ -public: - std::unordered_map Keywords; - std::unordered_map Variables; - - std::string Name(); - std::string Description(); - std::string Version(); - - std::vector Conflicts(); - std::vector Provides(); - std::vector Requires(bool priv = false); - - cmPkgConfigCflagsResult Cflags(bool priv = false); - cmPkgConfigLibsResult Libs(bool priv = false); - - cmPkgConfigEnv* env; - -private: - std::string StrOrDefault(std::string const& key, cm::string_view def = ""); -}; - class cmPkgConfigResolver { - friend class cmPkgConfigResult; - public: - static cm::optional ResolveStrict( - std::vector const& entries, cmPkgConfigEnv& env); + enum class Strictness + { + Strict, + Permissive, + BestEffort, + }; - static cm::optional ResolvePermissive( - std::vector const& entries, cmPkgConfigEnv& env); + cmPkgConfigResolver(cmPkgConfigParser parser, cmPkgConfigEnv const& env, + std::string const& pcFileDir); - static cmPkgConfigResult ResolveBestEffort( - std::vector const& entries, cmPkgConfigEnv& env); + static cm::optional Resolve( + cmPkgConfigParser parser, cmPkgConfigEnv const& env, Strictness strictness, + std::string const& pcFileDir); - static cmPkgConfigVersionReq ParseVersion(std::string const& version); + static cm::optional ResolveStrict( + cmPkgConfigParser parser, cmPkgConfigEnv const& env, + std::string const& pcFileDir); - static bool CheckVersion(cmPkgConfigVersionReq const& desired, + static cm::optional ResolvePermissive( + cmPkgConfigParser parser, cmPkgConfigEnv const& env, + std::string const& pcFileDir); + + static cmPkgConfigResolver ResolveBestEffort(cmPkgConfigParser parser, + cmPkgConfigEnv const& env, + std::string const& pcFileDir); + + std::string Literal(std::string const& key) const; + + std::string Name() const; + std::string Description() const; + std::string Version() const; + + std::vector Conflicts() const; + std::vector Provides() const; + std::vector Requires(bool priv = false) const; + + cmPkgConfigCflagsResult Cflags(bool priv = false) const; + cmPkgConfigLibsResult Libs(bool priv = false) const; + + static std::string VersionReqString(cmPkgConfigDependencySpec const& spec); + + static bool CheckVersion(cmPkgConfigDependencySpec const& desired, std::string const& provided); static void ReplaceSep(std::string& list); @@ -121,55 +100,23 @@ public: #endif private: - static std::string HandleVariablePermissive( - cmPkgConfigEntry const& entry, - std::unordered_map const& variables); + void ConfigureParser(std::string const& pcFileDir); + bool HasRequiredFields() const; + bool HasStrictConflicts() const; - static cm::optional HandleVariableStrict( - cmPkgConfigEntry const& entry, - std::unordered_map const& variables); - - static std::string HandleKeyword( - cmPkgConfigEntry const& entry, - std::unordered_map const& variables); - - static std::vector TokenizeFlags( - std::string const& flagline); + std::vector Fragments(std::string const& key) const; + std::vector Dependencies( + std::string const& key) const; static cmPkgConfigCflagsResult MangleCflags( - std::vector const& flags); - - static cmPkgConfigCflagsResult MangleCflags( - std::vector const& flags, std::string const& sysroot); - - static cmPkgConfigCflagsResult MangleCflags( - std::vector const& flags, - std::vector const& syspaths); - - static cmPkgConfigCflagsResult MangleCflags( - std::vector const& flags, std::string const& sysroot, - std::vector const& syspaths); + std::vector const& flags, cmPkgConfigEnv const& env); static cmPkgConfigLibsResult MangleLibs( - std::vector const& flags); + std::vector const& flags, cmPkgConfigEnv const& env); - static cmPkgConfigLibsResult MangleLibs( - std::vector const& flags, std::string const& sysroot); - - static cmPkgConfigLibsResult MangleLibs( - std::vector const& flags, - std::vector const& syspaths); - - static cmPkgConfigLibsResult MangleLibs( - std::vector const& flags, std::string const& sysroot, - std::vector const& syspaths); - - static std::string Reroot(cm::string_view flag, cm::string_view prefix, + static std::string Reroot(std::string const& flag, char const* prefix, std::string const& sysroot); - static cmPkgConfigVersionReq ParseVersion(std::string::const_iterator& cur, - std::string::const_iterator end); - - static std::vector ParseDependencies( - std::string const& deps); + mutable cmPkgConfigParser Parser; + cmPkgConfigEnv const* Env; }; diff --git a/Tests/Fuzzing/cmPkgConfigParserFuzzer.cxx b/Tests/Fuzzing/cmPkgConfigParserFuzzer.cxx index f4857f4464..4659bb6b9f 100644 --- a/Tests/Fuzzing/cmPkgConfigParserFuzzer.cxx +++ b/Tests/Fuzzing/cmPkgConfigParserFuzzer.cxx @@ -44,16 +44,21 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) result = parser.Finish(); (void)result; - // Access parsed data to exercise accessors - auto& entries = parser.Data(); - for (auto const& entry : entries) { - (void)entry.IsVariable; - (void)entry.Key; - for (auto const& elem : entry.Val) { - (void)elem.IsVariable; - (void)elem.Data; - } - } + parser.ParseComplete(); + + (void)parser.GetLiteral("Name"); + (void)parser.GetLiteral("Description"); + (void)parser.GetLiteral("Version"); + (void)parser.GetFragments("Cflags"); + (void)parser.GetFragments("Cflags.private"); + (void)parser.GetFragments("CFlags"); + (void)parser.GetFragments("CFlags.private"); + (void)parser.GetFragments("Libs"); + (void)parser.GetFragments("Libs.private"); + (void)parser.GetDependencies("Requires"); + (void)parser.GetDependencies("Requires.private"); + (void)parser.GetDependencies("Conflicts"); + (void)parser.GetDependencies("Provides"); return 0; } diff --git a/Tests/RunCMake/cmake_pkg_config/ExtractBuiltinDirs.cmake b/Tests/RunCMake/cmake_pkg_config/ExtractBuiltinDirs.cmake new file mode 100644 index 0000000000..8e350ffb02 --- /dev/null +++ b/Tests/RunCMake/cmake_pkg_config/ExtractBuiltinDirs.cmake @@ -0,0 +1,26 @@ +cmake_pkg_config( + EXTRACT builtindirs + PC_SYSROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/Sys Root" + TOP_BUILD_DIR "${CMAKE_CURRENT_LIST_DIR}/Top Build" +) + +set(expected_dir "${CMAKE_CURRENT_LIST_DIR}/PackageRoot") +set(expected_sysroot "${CMAKE_CURRENT_LIST_DIR}/Sys Root") +set(expected_top_build "${CMAKE_CURRENT_LIST_DIR}/Top Build") +set(expected_options + "-DROOT=${expected_sysroot}/include" + "-DTOP=${expected_top_build}/generated" +) + +if(NOT CMAKE_PKG_CONFIG_INCLUDES STREQUAL "-I${expected_sysroot}${expected_dir}/include") + message(FATAL_ERROR "Unexpected built-in directory include: ${CMAKE_PKG_CONFIG_INCLUDES}") +endif() + +if(NOT CMAKE_PKG_CONFIG_COMPILE_OPTIONS STREQUAL "${expected_options}") + message(FATAL_ERROR + "Unexpected built-in directory options: ${CMAKE_PKG_CONFIG_COMPILE_OPTIONS}") +endif() + +if(NOT CMAKE_PKG_CONFIG_LIBDIRS STREQUAL "-L${expected_sysroot}${expected_dir}/lib") + message(FATAL_ERROR "Unexpected built-in directory libdir: ${CMAKE_PKG_CONFIG_LIBDIRS}") +endif() diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/builtindirs.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/builtindirs.pc new file mode 100644 index 0000000000..10249d6740 --- /dev/null +++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/builtindirs.pc @@ -0,0 +1,9 @@ +Name: BuiltinDirs +Description: Uses built-in directory variables +Version: 1.0.0 + +includedir=${pcfiledir}/include +libdir=${pcfiledir}/lib + +Cflags: -I${includedir} -DROOT=${pc_sysrootdir}/include -DTOP=${pc_top_builddir}/generated +Libs: -L${libdir} -lbuiltindirs diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/invalid.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/invalid.pc index be1f43312a..2287478820 100644 --- a/Tests/RunCMake/cmake_pkg_config/PackageRoot/invalid.pc +++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/invalid.pc @@ -1,4 +1,4 @@ Name: Invalid Description: Will cause a parse error Version: 1.0.0 -BrokenKey +BrokenKey: ${unterminated diff --git a/Tests/RunCMake/cmake_pkg_config/RunCMakeTest.cmake b/Tests/RunCMake/cmake_pkg_config/RunCMakeTest.cmake index b2e38fc2f5..b2b72316ff 100644 --- a/Tests/RunCMake/cmake_pkg_config/RunCMakeTest.cmake +++ b/Tests/RunCMake/cmake_pkg_config/RunCMakeTest.cmake @@ -8,6 +8,7 @@ foreach(strictness IN ITEMS STRICT PERMISSIVE BEST_EFFORT) ) endforeach() +run_cmake(ExtractBuiltinDirs) run_cmake(ExtractEnv) run_cmake(ExtractFields) run_cmake(ExtractMangle)