| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmCMakePkgConfigCommand.h" | |
| #include <cstdio> | |
| #include <memory> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include <vector> | |
| #include <cm/filesystem> | |
| #include <cm/optional> | |
| #include <cm/string_view> | |
| #include <cmext/string_view> | |
| #include "cmsys/FStream.hxx" | |
| #include "cmArgumentParser.h" | |
| #include "cmArgumentParserTypes.h" | |
| #include "cmExecutionStatus.h" | |
| #include "cmList.h" | |
| #include "cmMakefile.h" | |
| #include "cmMessageType.h" | |
| #include "cmPkgConfigParser.h" | |
| #include "cmPkgConfigResolver.h" | |
| #include "cmStateTypes.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSubcommandTable.h" | |
| #include "cmSystemTools.h" | |
| #include "cmTarget.h" | |
| #include "cmTargetTypes.h" | |
| #include "cmValue.h" | |
| #include <cmllpkgc/llpkgc.h> | |
| // IWYU wants this | |
| namespace { | |
| struct ExtractArguments; | |
| struct PopulateArguments; | |
| struct ImportArguments; | |
| } | |
| namespace { | |
| cm::optional<std::string> GetPkgConfigBin(cmMakefile& mf) | |
| { | |
| cm::optional<std::string> result; | |
| auto pkgcfg = mf.GetDefinition("CMAKE_PKG_CONFIG_BIN"); | |
| if (pkgcfg.IsNOTFOUND()) { | |
| return result; | |
| } | |
| if (pkgcfg) { | |
| result = *pkgcfg; | |
| return result; | |
| } | |
| std::string path = cmSystemTools::FindProgram("pkgconf"); | |
| if (path.empty()) { | |
| path = cmSystemTools::FindProgram("pkg-config"); | |
| if (path.empty()) { | |
| mf.AddCacheDefinition("CMAKE_PKG_CONFIG_BIN", "pkg-config-NOTFOUND", | |
| "Location of pkg-config or pkgconf binary", | |
| cmStateEnums::FILEPATH); | |
| return result; | |
| } | |
| } | |
| mf.AddCacheDefinition("CMAKE_PKG_CONFIG_BIN", path, | |
| "Location of pkg-config or pkgconf binary", | |
| cmStateEnums::FILEPATH); | |
| result = std::move(path); | |
| return result; | |
| } | |
| std::vector<std::string> GetLocations(cmMakefile& mf, char const* cachevar, | |
| char const* envvar, char const* desc, | |
| char const* pcvar, bool need_pkgconf, | |
| std::vector<std::string> default_locs) | |
| { | |
| auto def = mf.GetDefinition(cachevar); | |
| if (def) { | |
| return cmList(def); | |
| } | |
| std::string paths; | |
| if (cmSystemTools::GetEnv(envvar, paths)) { | |
| cmPkgConfigResolver::ReplaceSep(paths); | |
| mf.AddCacheDefinition(cachevar, paths, desc, cmStateEnums::STRING); | |
| return cmList(paths); | |
| } | |
| auto pkgcfg = GetPkgConfigBin(mf); | |
| if (!pkgcfg || (need_pkgconf && (pkgcfg->find("pkgconf") == pkgcfg->npos))) { | |
| mf.AddCacheDefinition(cachevar, cmList::to_string(default_locs), desc, | |
| cmStateEnums::STRING); | |
| return default_locs; | |
| } | |
| std::string out; | |
| cmSystemTools::RunSingleCommand({ *pkgcfg, pcvar, "pkg-config" }, &out, | |
| nullptr, nullptr, nullptr, | |
| cmSystemTools::OUTPUT_NONE); | |
| cmPkgConfigResolver::ReplaceSep(out); | |
| out = cmTrimWhitespace(out); | |
| mf.AddCacheDefinition(cachevar, out, desc, cmStateEnums::STRING); | |
| return cmList(out); | |
| } | |
| std::vector<std::string> GetPcLibDirs(cmMakefile& mf) | |
| { | |
| std::vector<std::string> default_locs = { | |
| #ifndef _WIN32 | |
| "/usr/lib/pkgconfig", "/usr/share/pkgconfig" | |
| #endif | |
| }; | |
| return GetLocations(mf, "CMAKE_PKG_CONFIG_PC_LIB_DIRS", "PKG_CONFIG_LIBDIR", | |
| "Default search locations for package files", | |
| "--variable=pc_path", false, std::move(default_locs)); | |
| } | |
| std::vector<std::string> GetSysLibDirs(cmMakefile& mf) | |
| { | |
| std::vector<std::string> default_locs = { | |
| #ifndef _WIN32 | |
| "/lib", "/usr/lib" | |
| #endif | |
| }; | |
| return GetLocations( | |
| mf, "CMAKE_PKG_CONFIG_SYS_LIB_DIRS", "PKG_CONFIG_SYSTEM_LIBRARY_PATH", | |
| "System library directories filtered by flag mangling", | |
| "--variable=pc_system_libdirs", true, std::move(default_locs)); | |
| } | |
| std::vector<std::string> GetSysCflags(cmMakefile& mf) | |
| { | |
| std::vector<std::string> default_locs = { | |
| #ifndef _WIN32 | |
| "/usr/include" | |
| #endif | |
| }; | |
| return GetLocations( | |
| mf, "CMAKE_PKG_CONFIG_SYS_INCLUDE_DIRS", "PKG_CONFIG_SYSTEM_INCLUDE_PATH", | |
| "System include directories filtered by flag mangling", | |
| "--variable=pc_system_includedirs", true, std::move(default_locs)); | |
| } | |
| std::vector<std::string> GetPkgConfSysLibs(cmMakefile& mf) | |
| { | |
| auto def = mf.GetDefinition("CMAKE_PKG_CONFIG_PKGCONF_LIB_DIRS"); | |
| if (def) { | |
| return cmList(def); | |
| } | |
| std::string paths; | |
| if (!cmSystemTools::GetEnv("LIBRARY_PATH", paths)) { | |
| return {}; | |
| } | |
| cmPkgConfigResolver::ReplaceSep(paths); | |
| mf.AddCacheDefinition("CMAKE_PKG_CONFIG_PKGCONF_LIB_DIRS", paths, | |
| "Additional system library directories filtered by " | |
| "flag mangling in PKGCONF mode", | |
| cmStateEnums::STRING); | |
| return cmList(paths); | |
| } | |
| std::vector<std::string> GetPkgConfSysCflags(cmMakefile& mf) | |
| { | |
| auto def = mf.GetDefinition("CMAKE_PKG_CONFIG_PKGCONF_INCLUDES"); | |
| if (def) { | |
| return cmList(def); | |
| } | |
| std::string paths; | |
| auto get_and_append = [&](char const* var) { | |
| if (paths.empty()) { | |
| cmSystemTools::GetEnv(var, paths); | |
| } else { | |
| std::string tmp; | |
| cmSystemTools::GetEnv(var, tmp); | |
| if (!tmp.empty()) { | |
| paths = cmStrCat(std::move(paths), ';', tmp); | |
| } | |
| } | |
| }; | |
| get_and_append("CPATH"); | |
| get_and_append("C_INCLUDE_PATH"); | |
| get_and_append("CPLUS_INCLUDE_PATH"); | |
| get_and_append("OBJC_INCLUDE_PATH"); | |
| #ifdef _WIN32 | |
| get_and_append("INCLUDE"); | |
| #endif | |
| cmPkgConfigResolver::ReplaceSep(paths); | |
| mf.AddCacheDefinition("CMAKE_PKG_CONFIG_PKGCONF_INCLUDES", paths, | |
| "Additional system include directories filtered by " | |
| "flag mangling in PKGCONF mode", | |
| cmStateEnums::STRING); | |
| return cmList(paths); | |
| } | |
| std::vector<std::string> GetPcPath(cmMakefile& mf) | |
| { | |
| auto def = mf.GetDefinition("CMAKE_PKG_CONFIG_PC_PATH"); | |
| if (def) { | |
| return cmList(def); | |
| } | |
| std::string pcpath; | |
| if (cmSystemTools::GetEnv("PKG_CONFIG_PATH", pcpath)) { | |
| auto result = cmSystemTools::SplitString(pcpath, cmPkgConfigResolver::Sep); | |
| mf.AddCacheDefinition( | |
| "CMAKE_PKG_CONFIG_PC_PATH", cmList::to_string(result), | |
| "Additional search locations for package files", cmStateEnums::STRING); | |
| return result; | |
| } | |
| mf.AddCacheDefinition("CMAKE_PKG_CONFIG_PC_PATH", "", | |
| "Additional search locations for package files", | |
| cmStateEnums::STRING); | |
| return {}; | |
| } | |
| cm::optional<std::string> GetPath(cmMakefile& mf, char const* cachevar, | |
| char const* envvar, char const* desc) | |
| { | |
| cm::optional<std::string> result; | |
| auto def = mf.GetDefinition(cachevar); | |
| if (def) { | |
| result = *def; | |
| return result; | |
| } | |
| std::string path; | |
| if (cmSystemTools::GetEnv(envvar, path)) { | |
| mf.AddCacheDefinition(cachevar, path, desc, cmStateEnums::FILEPATH); | |
| result = std::move(path); | |
| return result; | |
| } | |
| return result; | |
| } | |
| cm::optional<std::string> GetSysrootDir(cmMakefile& mf) | |
| { | |
| return GetPath(mf, "CMAKE_PKG_CONFIG_SYSROOT_DIR", "PKG_CONFIG_SYSROOT_DIR", | |
| "System root used for re-rooting package includes and " | |
| "library directories"); | |
| } | |
| cm::optional<std::string> GetTopBuildDir(cmMakefile& mf) | |
| { | |
| return GetPath(mf, "CMAKE_PKG_CONFIG_TOP_BUILD_DIR", | |
| "PKG_CONFIG_TOP_BUILD_DIR", | |
| "Package file top_build_dir variable default value"); | |
| } | |
| bool GetBool(cmMakefile& mf, char const* cachevar, char const* envvar, | |
| char const* desc) | |
| { | |
| auto def = mf.GetDefinition(cachevar); | |
| if (def) { | |
| return def.IsOn(); | |
| } | |
| if (cmSystemTools::HasEnv(envvar)) { | |
| mf.AddCacheDefinition(cachevar, "ON", desc, cmStateEnums::BOOL); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool GetDisableUninstalled(cmMakefile& mf) | |
| { | |
| return GetBool(mf, "CMAKE_PKG_CONFIG_DISABLE_UNINSTALLED", | |
| "PKG_CONFIG_DISABLE_UNINSTALLED", | |
| "Disable search for `-uninstalled` (build tree) packages"); | |
| } | |
| bool GetAllowSysLibs(cmMakefile& mf) | |
| { | |
| return GetBool(mf, "CMAKE_PKG_CONFIG_ALLOW_SYS_LIBS", | |
| "PKG_CONFIG_ALLOW_SYSTEM_LIBS", | |
| "Allow system library directories during flag mangling"); | |
| } | |
| bool GetAllowSysInclude(cmMakefile& mf) | |
| { | |
| return GetBool(mf, "CMAKE_PKG_CONFIG_ALLOW_SYS_INCLUDES", | |
| "PKG_CONFIG_ALLOW_SYSTEM_CFLAGS", | |
| "Allow system include paths during flag manglging"); | |
| } | |
| struct CommonArguments : ArgumentParser::ParseResult | |
| { | |
| bool Required = false; | |
| bool Exact = false; | |
| bool Quiet = false; | |
| enum StrictnessType | |
| { | |
| STRICTNESS_STRICT, | |
| STRICTNESS_PERMISSIVE, | |
| STRICTNESS_BEST_EFFORT, | |
| }; | |
| StrictnessType Strictness = STRICTNESS_PERMISSIVE; | |
| std::string StrictnessError; | |
| ArgumentParser::Continue SetStrictness(cm::string_view strictness) | |
| { | |
| if (strictness == "STRICT"_s) { | |
| Strictness = STRICTNESS_STRICT; | |
| } else if (strictness == "PERMISSIVE"_s) { | |
| Strictness = STRICTNESS_PERMISSIVE; | |
| } else if (strictness == "BEST_EFFORT"_s) { | |
| Strictness = STRICTNESS_BEST_EFFORT; | |
| } else { | |
| StrictnessError = | |
| cmStrCat("Invalid 'STRICTNESS' '", strictness, | |
| "'; must be one of 'STRICT', 'PERMISSIVE', or 'BEST_EFFORT'"); | |
| } | |
| return ArgumentParser::Continue::Yes; | |
| } | |
| enum EnvModeType | |
| { | |
| ENVMODE_FDO, | |
| ENVMODE_PKGCONF, | |
| ENVMODE_IGNORE, | |
| }; | |
| EnvModeType EnvMode = ENVMODE_PKGCONF; | |
| std::string EnvModeError; | |
| ArgumentParser::Continue SetEnvMode(cm::string_view envMode) | |
| { | |
| if (envMode == "FDO"_s) { | |
| EnvMode = ENVMODE_FDO; | |
| } else if (envMode == "PKGCONF"_s) { | |
| EnvMode = ENVMODE_PKGCONF; | |
| } else if (envMode == "IGNORE"_s) { | |
| EnvMode = ENVMODE_IGNORE; | |
| } else { | |
| EnvModeError = | |
| cmStrCat("Invalid 'ENV_MODE' '", envMode, | |
| "'; must be one of 'FDO', 'PKGCONF', or 'IGNORE'"); | |
| } | |
| return ArgumentParser::Continue::Yes; | |
| } | |
| cm::optional<std::string> Package; | |
| cm::optional<std::string> Version; | |
| cm::optional<std::string> SysrootDir; | |
| cm::optional<std::string> TopBuildDir; | |
| cm::optional<bool> DisableUninstalled; | |
| cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> PcPath; | |
| cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> PcLibdir; | |
| bool CheckArgs(cmExecutionStatus& status) const | |
| { | |
| if (!Package) { | |
| status.SetError("A package name or absolute path must be specified"); | |
| return false; | |
| } | |
| if (!StrictnessError.empty()) { | |
| status.SetError(StrictnessError); | |
| return false; | |
| } | |
| if (!EnvModeError.empty()) { | |
| status.SetError(EnvModeError); | |
| return false; | |
| } | |
| return true; | |
| } | |
| }; | |
| #define BIND_COMMON(argtype) \ | |
| (cmArgumentParser<argtype>{}) \ | |
| .Bind(1, &argtype::Package) \ | |
| .Bind(2, &argtype::Version) \ | |
| .Bind("REQUIRED"_s, &argtype::Required) \ | |
| .Bind("EXACT"_s, &argtype::Exact) \ | |
| .Bind("QUIET"_s, &argtype::Quiet) \ | |
| .Bind("STRICTNESS"_s, &argtype::SetStrictness) \ | |
| .Bind("ENV_MODE"_s, &argtype::SetEnvMode) \ | |
| .Bind("PC_SYSROOT_DIR"_s, &argtype::SysrootDir) \ | |
| .Bind("TOP_BUILD_DIR"_s, &argtype::TopBuildDir) \ | |
| .Bind("DISABLE_UNINSTALLED"_s, &argtype::DisableUninstalled) \ | |
| .Bind("PC_LIBDIR"_s, &argtype::PcLibdir) \ | |
| .Bind("PC_PATH"_s, &argtype::PcPath) | |
| void CollectEnv(cmMakefile& mf, cmPkgConfigEnv& env, | |
| CommonArguments::EnvModeType mode) | |
| { | |
| if (mode == CommonArguments::EnvModeType::ENVMODE_IGNORE) { | |
| return; | |
| } | |
| if (!env.Path) { | |
| env.Path = GetPcPath(mf); | |
| } | |
| if (!env.LibDirs) { | |
| env.LibDirs = GetPcLibDirs(mf); | |
| } | |
| if (!env.DisableUninstalled) { | |
| env.DisableUninstalled = GetDisableUninstalled(mf); | |
| } | |
| if (!env.SysrootDir) { | |
| env.SysrootDir = GetSysrootDir(mf); | |
| } | |
| if (!env.TopBuildDir) { | |
| env.TopBuildDir = GetTopBuildDir(mf); | |
| } | |
| env.AllowSysCflags = GetAllowSysInclude(mf); | |
| env.SysCflags = GetSysCflags(mf); | |
| env.AllowSysLibs = GetAllowSysLibs(mf); | |
| env.SysLibs = GetSysLibDirs(mf); | |
| if (mode == CommonArguments::EnvModeType::ENVMODE_FDO) { | |
| return; | |
| } | |
| *env.SysCflags += GetPkgConfSysCflags(mf); | |
| *env.SysLibs += GetPkgConfSysLibs(mf); | |
| } | |
| struct ImportEnv | |
| { | |
| bool required; | |
| bool quiet; | |
| bool exact; | |
| CommonArguments::StrictnessType strictness; | |
| }; | |
| struct ImportState | |
| { | |
| cmExecutionStatus& status; | |
| bool err; | |
| }; | |
| void warn_or_error(std::string const& err, ImportEnv imEnv, ImportState& state) | |
| { | |
| if (imEnv.required) { | |
| state.status.SetError(err); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| } else if (!imEnv.quiet) { | |
| state.status.GetMakefile().IssueMessage(MessageType::WARNING, err); | |
| } | |
| state.err = true; | |
| } | |
| cm::optional<cmPkgConfigDependencySpec> MakeVersionRequirement( | |
| std::string const& version) | |
| { | |
| 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<cmPkgConfigResolver> ReadPackage(std::string const& package, | |
| ImportEnv imEnv, | |
| cmPkgConfigEnv const& pcEnv, | |
| ImportState& state) | |
| { | |
| cm::optional<cmPkgConfigResolver> result; | |
| cm::filesystem::path path{ package }; | |
| if (path.extension() == ".pc") { | |
| if (!cmSystemTools::FileExists(path.string())) { | |
| return result; | |
| } | |
| } else { | |
| if (pcEnv.DisableUninstalled && !*pcEnv.DisableUninstalled) { | |
| auto uninstalled = path; | |
| uninstalled.concat("-uninstalled.pc"); | |
| uninstalled = | |
| cmSystemTools::FindFile(uninstalled.string(), pcEnv.search, true); | |
| if (uninstalled.empty()) { | |
| path = cmSystemTools::FindFile(path.concat(".pc").string(), | |
| pcEnv.search, true); | |
| if (path.empty()) { | |
| return result; | |
| } | |
| } else { | |
| path = uninstalled; | |
| } | |
| } else { | |
| path = cmSystemTools::FindFile(path.concat(".pc").string(), pcEnv.search, | |
| true); | |
| if (path.empty()) { | |
| return result; | |
| } | |
| } | |
| } | |
| auto len = cmSystemTools::FileLength(path.string()); | |
| // Windows requires this weird string -> c_str dance | |
| cmsys::ifstream ifs(path.string().c_str(), std::ios::binary); | |
| if (!ifs) { | |
| warn_or_error(cmStrCat("Could not open file '", path.string(), '\''), | |
| imEnv, state); | |
| return result; | |
| } | |
| std::unique_ptr<char[]> buf(new char[len]); | |
| ifs.read(buf.get(), len); | |
| // 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, state); | |
| return result; | |
| } | |
| using StrictnessType = CommonArguments::StrictnessType; | |
| cmPkgConfigParser parser; | |
| auto err = parser.Finish(buf.get(), len); | |
| if (imEnv.strictness != StrictnessType::STRICTNESS_BEST_EFFORT && | |
| err != PFCE_OK) { | |
| warn_or_error(cmStrCat("Parsing failed for file '", path.string(), '\''), | |
| imEnv, state); | |
| return result; | |
| } | |
| std::string const pcFileDir = cmSystemTools::GetFilenamePath( | |
| cmSystemTools::CollapseFullPath(path.string())); | |
| if (imEnv.strictness == StrictnessType::STRICTNESS_STRICT) { | |
| result = | |
| cmPkgConfigResolver::ResolveStrict(std::move(parser), pcEnv, pcFileDir); | |
| } else if (imEnv.strictness == StrictnessType::STRICTNESS_PERMISSIVE) { | |
| result = cmPkgConfigResolver::ResolvePermissive(std::move(parser), pcEnv, | |
| pcFileDir); | |
| } else { | |
| result = cmPkgConfigResolver::ResolveBestEffort(std::move(parser), pcEnv, | |
| pcFileDir); | |
| } | |
| if (!result) { | |
| warn_or_error( | |
| cmStrCat("Resolution failed for file '", path.string(), '\''), imEnv, | |
| state); | |
| } | |
| return result; | |
| } | |
| cm::optional<cmPkgConfigResolver> ImportPackage( | |
| std::string const& package, cm::optional<std::string> version, | |
| ImportEnv imEnv, cmPkgConfigEnv const& pcEnv, ImportState& state) | |
| { | |
| auto result = ReadPackage(package, imEnv, pcEnv, state); | |
| if (!result) { | |
| if (!state.err) { | |
| warn_or_error(cmStrCat("Could not find pkg-config: '", package, '\''), | |
| imEnv, state); | |
| } | |
| return result; | |
| } | |
| if (imEnv.exact) { | |
| std::string ver; | |
| if (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, state); | |
| return {}; | |
| } | |
| } else if (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, state); | |
| return {}; | |
| } | |
| } | |
| return result; | |
| } | |
| struct pkgStackEntry | |
| { | |
| cmPkgConfigDependencySpec Dependency; | |
| std::string parent; | |
| }; | |
| cm::optional<cmPkgConfigResolver> ImportPackage( | |
| std::string const& package, std::vector<pkgStackEntry> const& reqs, | |
| ImportEnv imEnv, cmPkgConfigEnv const& pcEnv, ImportState& state) | |
| { | |
| auto result = ReadPackage(package, imEnv, pcEnv, state); | |
| if (!result) { | |
| if (!state.err) { | |
| std::string req_str = cmStrCat('\'', reqs.begin()->parent, '\''); | |
| for (auto it = reqs.begin() + 1; it != reqs.end(); ++it) { | |
| req_str = cmStrCat(std::move(req_str), ", '", it->parent, '\''); | |
| } | |
| warn_or_error(cmStrCat("Could not find pkg-config: '", package, | |
| "' required by: ", req_str), | |
| imEnv, state); | |
| } | |
| return result; | |
| } | |
| auto ver = result->Version(); | |
| for (auto const& req : reqs) { | |
| 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 {}; | |
| } | |
| } | |
| return result; | |
| } | |
| cm::optional<std::pair<cmPkgConfigEnv, ImportEnv>> HandleCommon( | |
| CommonArguments& args, cmExecutionStatus& status) | |
| { | |
| auto& mf = status.GetMakefile(); | |
| if (!args.CheckArgs(status)) { | |
| return {}; | |
| } | |
| cmPkgConfigEnv pcEnv; | |
| if (args.PcLibdir) { | |
| pcEnv.LibDirs = std::move(*args.PcLibdir); | |
| } | |
| if (args.PcPath) { | |
| pcEnv.Path = std::move(*args.PcPath); | |
| } | |
| pcEnv.DisableUninstalled = args.DisableUninstalled; | |
| if (args.SysrootDir) { | |
| pcEnv.SysrootDir = std::move(*args.SysrootDir); | |
| } | |
| if (args.TopBuildDir) { | |
| pcEnv.TopBuildDir = std::move(*args.TopBuildDir); | |
| } | |
| CollectEnv(mf, pcEnv, args.EnvMode); | |
| if (pcEnv.Path) { | |
| pcEnv.search = *pcEnv.Path; | |
| if (pcEnv.LibDirs) { | |
| pcEnv.search += *pcEnv.LibDirs; | |
| } | |
| } else if (pcEnv.LibDirs) { | |
| pcEnv.search = *pcEnv.LibDirs; | |
| } | |
| return std::pair<cmPkgConfigEnv, ImportEnv>{ | |
| pcEnv, { args.Required, args.Quiet, args.Exact, args.Strictness } | |
| }; | |
| } | |
| struct ExtractArguments : CommonArguments | |
| { | |
| cm::optional<bool> AllowSystemIncludes; | |
| cm::optional<bool> AllowSystemLibs; | |
| cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> | |
| SystemIncludeDirs; | |
| cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> | |
| SystemLibraryDirs; | |
| }; | |
| auto const ExtractParser = | |
| BIND_COMMON(ExtractArguments) | |
| .Bind("ALLOW_SYSTEM_INCLUDES"_s, &ExtractArguments::AllowSystemIncludes) | |
| .Bind("ALLOW_SYSTEM_LIBS"_s, &ExtractArguments::AllowSystemLibs) | |
| .Bind("SYSTEM_INCLUDE_DIRS"_s, &ExtractArguments::SystemIncludeDirs) | |
| .Bind("SYSTEM_LIBRARY_DIRS"_s, &ExtractArguments::SystemLibraryDirs); | |
| bool HandleExtractCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| std::vector<std::string> unparsed; | |
| auto parsedArgs = ExtractParser.Parse(args, &unparsed); | |
| auto maybeEnv = HandleCommon(parsedArgs, status); | |
| if (!maybeEnv) { | |
| return !parsedArgs.Required; | |
| } | |
| auto& pcEnv = maybeEnv->first; | |
| auto const& imEnv = maybeEnv->second; | |
| ImportState state{ status, false }; | |
| auto maybePackage = ImportPackage(*parsedArgs.Package, parsedArgs.Version, | |
| imEnv, pcEnv, state); | |
| if (!maybePackage) { | |
| return !parsedArgs.Required; | |
| } | |
| auto& package = *maybePackage; | |
| if (parsedArgs.AllowSystemIncludes) { | |
| pcEnv.AllowSysCflags = *parsedArgs.AllowSystemIncludes; | |
| } | |
| if (parsedArgs.AllowSystemLibs) { | |
| pcEnv.AllowSysLibs = *parsedArgs.AllowSystemLibs; | |
| } | |
| if (parsedArgs.SystemIncludeDirs) { | |
| pcEnv.SysCflags = *parsedArgs.SystemIncludeDirs; | |
| } | |
| if (parsedArgs.SystemLibraryDirs) { | |
| pcEnv.SysLibs = *parsedArgs.SystemLibraryDirs; | |
| } | |
| auto& mf = status.GetMakefile(); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_NAME", package.Name()); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_DESCRIPTION", package.Description()); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_VERSION", package.Version()); | |
| auto make_list = [&](char const* def, | |
| std::vector<cmPkgConfigDependencySpec> const& deps) { | |
| std::vector<cm::string_view> vec; | |
| vec.reserve(deps.size()); | |
| for (auto const& dep : deps) { | |
| vec.emplace_back(dep.Name); | |
| } | |
| mf.AddDefinition(def, cmList::to_string(vec)); | |
| }; | |
| make_list("CMAKE_PKG_CONFIG_CONFLICTS", package.Conflicts()); | |
| make_list("CMAKE_PKG_CONFIG_PROVIDES", package.Provides()); | |
| make_list("CMAKE_PKG_CONFIG_REQUIRES", package.Requires()); | |
| make_list("CMAKE_PKG_CONFIG_REQUIRES_PRIVATE", package.Requires(true)); | |
| auto cflags = package.Cflags(); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_CFLAGS", cflags.Flagline); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_INCLUDES", | |
| cmList::to_string(cflags.Includes)); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_COMPILE_OPTIONS", | |
| cmList::to_string(cflags.CompileOptions)); | |
| cflags = package.Cflags(true); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_CFLAGS_PRIVATE", cflags.Flagline); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_INCLUDES_PRIVATE", | |
| cmList::to_string(cflags.Includes)); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_COMPILE_OPTIONS_PRIVATE", | |
| cmList::to_string(cflags.CompileOptions)); | |
| auto libs = package.Libs(); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_LIBS", libs.Flagline); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_LIBDIRS", | |
| cmList::to_string(libs.LibDirs)); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_LIBNAMES", | |
| cmList::to_string(libs.LibNames)); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_LINK_OPTIONS", | |
| cmList::to_string(libs.LinkOptions)); | |
| libs = package.Libs(true); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_LIBS_PRIVATE", libs.Flagline); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_LIBDIRS_PRIVATE", | |
| cmList::to_string(libs.LibDirs)); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_LIBNAMES_PRIVATE", | |
| cmList::to_string(libs.LibNames)); | |
| mf.AddDefinition("CMAKE_PKG_CONFIG_LINK_OPTIONS_PRIVATE", | |
| cmList::to_string(libs.LinkOptions)); | |
| return true; | |
| } | |
| using pkgStack = std::unordered_map<std::string, std::vector<pkgStackEntry>>; | |
| using pkgProviders = std::unordered_map<std::string, std::string>; | |
| cmTarget* CreateCMakeTarget(std::string const& name, std::string const& prefix, | |
| cmPkgConfigResolver const& pkg, | |
| pkgProviders const& providers, cmMakefile& mf) | |
| { | |
| auto* tgt = mf.AddForeignTarget("pkgcfg", cmStrCat(prefix, name)); | |
| tgt->SetForeignPackageName(name); | |
| tgt->AppendProperty("VERSION", pkg.Version()); | |
| auto libs = pkg.Libs(); | |
| for (auto const& flag : libs.LibNames) { | |
| tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", flag.substr(2)); | |
| } | |
| for (auto const& flag : libs.LibDirs) { | |
| tgt->AppendProperty("INTERFACE_LINK_DIRECTORIES", flag.substr(2)); | |
| } | |
| tgt->AppendProperty("INTERFACE_LINK_OPTIONS", | |
| cmList::to_string(libs.LinkOptions)); | |
| auto cflags = pkg.Cflags(); | |
| for (auto const& flag : cflags.Includes) { | |
| tgt->AppendProperty("INTERFACE_INCLUDE_DIRECTORIES", flag.substr(2)); | |
| } | |
| tgt->AppendProperty("INTERFACE_COMPILE_OPTIONS", | |
| cmList::to_string(cflags.CompileOptions)); | |
| for (auto const& dep : pkg.Requires()) { | |
| auto it = providers.find(dep.Name); | |
| if (it != providers.end()) { | |
| tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", it->second); | |
| continue; | |
| } | |
| tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", | |
| cmStrCat("@foreign_pkgcfg::", prefix, dep.Name)); | |
| } | |
| return tgt; | |
| } | |
| bool CheckPackageDependencies( | |
| std::string const& name, std::string const& prefix, | |
| cmPkgConfigResolver const& pkg, pkgStack& inStack, | |
| std::unordered_map<std::string, cmPkgConfigResolver> const& outStack, | |
| pkgProviders const& providers, ImportEnv imEnv, ImportState& state) | |
| { | |
| for (auto const& dep : pkg.Requires()) { | |
| auto prov_it = providers.find(dep.Name); | |
| if (prov_it != providers.end()) { | |
| continue; | |
| } | |
| 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, *ver)) { | |
| warn_or_error(cmStrCat("Package '", dep.Name, "' version '", *ver, | |
| "' does not meet version requirement '", | |
| cmPkgConfigResolver::VersionReqString(dep), | |
| "' of '", name, '\''), | |
| imEnv, state); | |
| return false; | |
| } | |
| continue; | |
| } | |
| auto it = outStack.find(dep.Name); | |
| if (it != outStack.end()) { | |
| auto ver = it->second.Version(); | |
| if (!cmPkgConfigResolver::CheckVersion(dep, ver)) { | |
| warn_or_error(cmStrCat("Package '", dep.Name, "' version '", ver, | |
| "' does not meet version requirement '", | |
| cmPkgConfigResolver::VersionReqString(dep), | |
| "' of '", name, '\''), | |
| imEnv, state); | |
| return false; | |
| } | |
| continue; | |
| } | |
| inStack[dep.Name].emplace_back(pkgStackEntry{ dep, name }); | |
| } | |
| return true; | |
| } | |
| struct PopulateArguments : CommonArguments | |
| { | |
| cm::optional<std::string> Prefix; | |
| cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Providers; | |
| }; | |
| #define BIND_POPULATE(argtype) \ | |
| BIND_COMMON(argtype) \ | |
| .Bind("PREFIX"_s, &argtype::Prefix) \ | |
| .Bind("BIND_PC_REQUIRES"_s, &argtype::Providers) | |
| auto const PopulateParser = BIND_POPULATE(PopulateArguments); | |
| std::pair<bool, bool> PopulatePCTarget(PopulateArguments& args, | |
| cmExecutionStatus& status) | |
| { | |
| std::string prefix = args.Prefix ? cmStrCat(*args.Prefix, "_"_s) : ""; | |
| auto& mf = status.GetMakefile(); | |
| auto maybeEnv = HandleCommon(args, status); | |
| if (!maybeEnv) { | |
| return { !args.Required, false }; | |
| } | |
| auto& pcEnv = maybeEnv->first; | |
| 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) { | |
| auto assignment = provider_str.find('='); | |
| if (assignment != std::string::npos) { | |
| providers.emplace(provider_str.substr(0, assignment), | |
| provider_str.substr(assignment + 1)); | |
| } else { | |
| state.status.SetError(cmStrCat( | |
| "No '=' found in BIND_PC_REQUIRES argument '", provider_str, '\'')); | |
| cmSystemTools::SetFatalErrorOccurred(); | |
| return { false, false }; | |
| } | |
| } | |
| } | |
| pkgStack inStack; | |
| std::unordered_map<std::string, cmPkgConfigResolver> outStack; | |
| auto maybePackage = | |
| ImportPackage(*args.Package, args.Version, imEnv, pcEnv, state); | |
| if (!maybePackage) { | |
| return { !args.Required, false }; | |
| } | |
| ImportEnv depEnv = imEnv; | |
| depEnv.exact = false; | |
| if (!CheckPackageDependencies(*args.Package, prefix, *maybePackage, inStack, | |
| outStack, providers, depEnv, state)) { | |
| return { !args.Required, false }; | |
| } | |
| outStack.emplace(*args.Package, std::move(*maybePackage)); | |
| while (!inStack.empty()) { | |
| auto name = inStack.begin()->first; | |
| auto reqs = inStack.begin()->second; | |
| maybePackage = ImportPackage(name, reqs, depEnv, pcEnv, state); | |
| if (!maybePackage) { | |
| return { !args.Required, false }; | |
| } | |
| if (!CheckPackageDependencies(name, prefix, *maybePackage, inStack, | |
| outStack, providers, depEnv, state)) { | |
| return { !args.Required, false }; | |
| } | |
| inStack.erase(name); | |
| outStack.emplace(std::move(name), std::move(*maybePackage)); | |
| } | |
| for (auto& entry : outStack) { | |
| CreateCMakeTarget(entry.first, prefix, entry.second, providers, mf); | |
| } | |
| return { true, true }; | |
| } | |
| bool HandlePopulateCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| std::vector<std::string> unparsed; | |
| auto parsedArgs = PopulateParser.Parse(args, &unparsed); | |
| std::string prefix = | |
| parsedArgs.Prefix ? cmStrCat(*parsedArgs.Prefix, "_"_s) : ""; | |
| auto foreign_name = | |
| cmStrCat("@foreign_pkgcfg::", prefix, *parsedArgs.Package); | |
| auto found_var = cmStrCat("PKGCONFIG_", *parsedArgs.Package, "_FOUND"); | |
| auto& mf = status.GetMakefile(); | |
| if (mf.FindTargetToUse(foreign_name, cm::TargetDomain::FOREIGN)) { | |
| mf.AddDefinition(found_var, "TRUE"); | |
| return true; | |
| } | |
| auto result = PopulatePCTarget(parsedArgs, status); | |
| mf.AddDefinition(found_var, result.second ? "TRUE" : "FALSE"); | |
| return result.first; | |
| } | |
| struct ImportArguments : PopulateArguments | |
| { | |
| cm::optional<std::string> Name; | |
| }; | |
| auto const ImportParser = | |
| BIND_POPULATE(ImportArguments).Bind("NAME"_s, &ImportArguments::Name); | |
| bool HandleImportCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| std::vector<std::string> unparsed; | |
| auto parsedArgs = ImportParser.Parse(args, &unparsed); | |
| std::string prefix = | |
| parsedArgs.Prefix ? cmStrCat(*parsedArgs.Prefix, "_"_s) : ""; | |
| auto foreign_name = | |
| cmStrCat("@foreign_pkgcfg::", prefix, *parsedArgs.Package); | |
| auto local_name = | |
| cmStrCat("PkgConfig::", parsedArgs.Name.value_or(*parsedArgs.Package)); | |
| auto found_var = cmStrCat("PKGCONFIG_", *parsedArgs.Package, "_FOUND"); | |
| auto& mf = status.GetMakefile(); | |
| if (mf.FindTargetToUse(local_name)) { | |
| mf.AddDefinition(found_var, "TRUE"); | |
| return true; | |
| } | |
| if (!mf.FindTargetToUse(foreign_name, cm::TargetDomain::FOREIGN)) { | |
| auto result = PopulatePCTarget(parsedArgs, status); | |
| if (!result.second) { | |
| mf.AddDefinition(found_var, "FALSE"); | |
| return result.first; | |
| } | |
| } | |
| mf.AddDefinition(found_var, "TRUE"); | |
| auto* tgt = | |
| mf.AddImportedTarget(local_name, cm::TargetType::INTERFACE_LIBRARY, | |
| cm::ImportedTargetScope::Local); | |
| tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", foreign_name); | |
| if (auto* ft = mf.FindTargetToUse(foreign_name, cm::TargetDomain::FOREIGN)) { | |
| tgt->SetForeignTarget(ft); | |
| } | |
| return true; | |
| } | |
| bool PkgConfigImportPackageImpl(cmMakefile& mf, cmExecutionStatus& status, | |
| std::string const& name) | |
| { | |
| std::string const prefix; | |
| // A pkg-config package is referenced in CPS as "<name>:<name>" and linked | |
| // directly as the foreign target which provides it, so there is no | |
| // interface target to create here. | |
| auto const foreignName = cmStrCat("@foreign_pkgcfg::", prefix, name); | |
| if (mf.FindTargetToUse(foreignName, cm::TargetDomain::FOREIGN)) { | |
| return true; | |
| } | |
| cmPkgConfigEnv pcEnv; | |
| CollectEnv(mf, pcEnv, CommonArguments::EnvModeType::ENVMODE_PKGCONF); | |
| if (pcEnv.Path) { | |
| pcEnv.search = *pcEnv.Path; | |
| if (pcEnv.LibDirs) { | |
| pcEnv.search += *pcEnv.LibDirs; | |
| } | |
| } else if (pcEnv.LibDirs) { | |
| pcEnv.search = *pcEnv.LibDirs; | |
| } | |
| ImportEnv imEnv{ false, true, false, | |
| CommonArguments::StrictnessType::STRICTNESS_PERMISSIVE }; | |
| ImportState state{ status, false }; | |
| cm::optional<cmPkgConfigResolver> maybePackage = | |
| ImportPackage(name, cm::optional<std::string>{}, imEnv, pcEnv, state); | |
| if (!maybePackage) { | |
| return false; | |
| } | |
| pkgProviders providers; | |
| pkgStack inStack; | |
| std::unordered_map<std::string, cmPkgConfigResolver> outStack; | |
| ImportEnv depEnv = imEnv; | |
| depEnv.exact = false; | |
| if (!CheckPackageDependencies(name, prefix, *maybePackage, inStack, outStack, | |
| providers, depEnv, state)) { | |
| return false; | |
| } | |
| outStack.emplace(name, std::move(*maybePackage)); | |
| while (!inStack.empty()) { | |
| auto depName = inStack.begin()->first; | |
| auto reqs = inStack.begin()->second; | |
| maybePackage = ImportPackage(depName, reqs, depEnv, pcEnv, state); | |
| if (!maybePackage) { | |
| return false; | |
| } | |
| if (!CheckPackageDependencies(depName, prefix, *maybePackage, inStack, | |
| outStack, providers, depEnv, state)) { | |
| return false; | |
| } | |
| inStack.erase(depName); | |
| outStack.emplace(std::move(depName), std::move(*maybePackage)); | |
| } | |
| for (auto& entry : outStack) { | |
| CreateCMakeTarget(entry.first, prefix, entry.second, providers, mf); | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| bool cmImportPkgConfigPackage(cmMakefile& mf, cmExecutionStatus& status, | |
| std::string const& name) | |
| { | |
| return PkgConfigImportPackageImpl(mf, status, name); | |
| } | |
| bool cmCMakePkgConfigCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| if (args.size() < 2) { | |
| status.SetError("must be called with at least two arguments."); | |
| return false; | |
| } | |
| static cmSubcommandTable const subcommand{ | |
| { "EXTRACT"_s, HandleExtractCommand }, | |
| { "POPULATE"_s, HandlePopulateCommand }, | |
| { "IMPORT"_s, HandleImportCommand }, | |
| }; | |
| return subcommand(args[0], args, status); | |
| } |