/* 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 #include #include #include #include #include #include #include #include #include #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 "cmValue.h" #include // IWYU wants this namespace { struct ExtractArguments; struct PopulateArguments; struct ImportArguments; } namespace { cm::optional GetPkgConfigBin(cmMakefile& mf) { cm::optional 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 GetLocations(cmMakefile& mf, char const* cachevar, char const* envvar, char const* desc, char const* pcvar, bool need_pkgconf, std::vector 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 GetPcLibDirs(cmMakefile& mf) { std::vector 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 GetSysLibDirs(cmMakefile& mf) { std::vector 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 GetSysCflags(cmMakefile& mf) { std::vector 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 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 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 += ";" + 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 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 GetPath(cmMakefile& mf, char const* cachevar, char const* envvar, char const* desc) { cm::optional 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 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 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 Package; cm::optional Version; cm::optional SysrootDir; cm::optional TopBuildDir; cm::optional DisableUninstalled; cm::optional>> PcPath; cm::optional>> 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{}) \ .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; bool err; CommonArguments::StrictnessType strictness; cmExecutionStatus& status; }; void warn_or_error(std::string const& err, ImportEnv& imEnv) { if (imEnv.required) { imEnv.status.SetError(err); cmSystemTools::SetFatalErrorOccurred(); } else if (!imEnv.quiet) { imEnv.status.GetMakefile().IssueMessage(MessageType::WARNING, err); } imEnv.err = true; } cm::optional ReadPackage(std::string const& package, ImportEnv& imEnv, cmPkgConfigEnv& pcEnv) { cm::optional 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); return result; } std::unique_ptr 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); return result; } using StrictnessType = CommonArguments::StrictnessType; cmPkgConfigParser parser; auto err = parser.Finish(buf.get(), len); if (imEnv.strictness != StrictnessType::STRICTNESS_BEST_EFFORT && err != PCE_OK) { warn_or_error(cmStrCat("Parsing failed for file '", path.string(), "'"), imEnv); return result; } if (imEnv.strictness == StrictnessType::STRICTNESS_STRICT) { result = cmPkgConfigResolver::ResolveStrict(parser.Data(), pcEnv); } else if (imEnv.strictness == StrictnessType::STRICTNESS_PERMISSIVE) { result = cmPkgConfigResolver::ResolvePermissive(parser.Data(), pcEnv); } else { result = cmPkgConfigResolver::ResolveBestEffort(parser.Data(), pcEnv); } if (!result) { warn_or_error(cmStrCat("Resolution failed for file '", path.string(), "'"), imEnv); } return result; } cm::optional ImportPackage( std::string const& package, cm::optional version, ImportEnv& imEnv, cmPkgConfigEnv& pcEnv) { auto result = ReadPackage(package, imEnv, pcEnv); if (!result) { if (!imEnv.err) { warn_or_error(cmStrCat("Could not find pkg-config: '", package, "'"), imEnv); } return result; } if (imEnv.exact) { std::string ver; if (version) { ver = cmPkgConfigResolver::ParseVersion(*version).Version; } if (ver != result->Version()) { warn_or_error( cmStrCat("Package '", package, "' version '", result->Version(), "' does not meet exact version requirement '", ver, "'"), imEnv); return {}; } } else if (version) { auto rv = cmPkgConfigResolver::ParseVersion(*version); if (!cmPkgConfigResolver::CheckVersion(rv, result->Version())) { warn_or_error( cmStrCat("Package '", package, "' version '", result->Version(), "' does not meet version requirement '", *version, "'"), imEnv); return {}; } } result->env = &pcEnv; return result; } struct pkgStackEntry { cmPkgConfigVersionReq ver; std::string parent; }; cm::optional ImportPackage( std::string const& package, std::vector const& reqs, ImportEnv& imEnv, cmPkgConfigEnv& pcEnv) { auto result = ReadPackage(package, imEnv, pcEnv); if (!result) { if (!imEnv.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); } return result; } 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); return {}; } } result->env = &pcEnv; return result; } cm::optional> 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{ pcEnv, { args.Required, args.Quiet, args.Exact, false, args.Strictness, status } }; } struct ExtractArguments : CommonArguments { cm::optional AllowSystemIncludes; cm::optional AllowSystemLibs; cm::optional>> SystemIncludeDirs; cm::optional>> 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 const& args, cmExecutionStatus& status) { std::vector unparsed; auto parsedArgs = ExtractParser.Parse(args, &unparsed); auto maybeEnv = HandleCommon(parsedArgs, status); if (!maybeEnv) { return !parsedArgs.Required; } auto& pcEnv = maybeEnv->first; auto& imEnv = maybeEnv->second; auto maybePackage = ImportPackage(*parsedArgs.Package, parsedArgs.Version, imEnv, pcEnv); 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 const& deps) { std::vector 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>; using pkgProviders = std::unordered_map; cmTarget* CreateCMakeTarget(std::string const& name, std::string const& prefix, cmPkgConfigResult& pkg, pkgProviders& providers, cmMakefile& mf) { auto* tgt = mf.AddForeignTarget("pkgcfg", cmStrCat(prefix, 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& 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, cmPkgConfigResult& pkg, pkgStack& inStack, std::unordered_map& outStack, pkgProviders& providers, ImportEnv& imEnv) { for (auto& dep : pkg.Requires()) { auto prov_it = providers.find(dep.Name); if (prov_it != providers.end()) { continue; } auto* tgt = imEnv.status.GetMakefile().FindTargetToUse( cmStrCat("@foreign_pkgcfg::", prefix, dep.Name), cmStateEnums::TargetDomain::FOREIGN); if (tgt) { auto ver = tgt->GetProperty("VERSION"); if (!cmPkgConfigResolver::CheckVersion(dep.VerReq, *ver)) { warn_or_error(cmStrCat("Package '", dep.Name, "' version '", *ver, "' does not meet version requirement '", dep.VerReq.string(), "' ", "of '", name, "'"), imEnv); return false; } continue; } auto it = outStack.find(dep.Name); if (it != outStack.end()) { auto ver = it->second.Version(); if (!cmPkgConfigResolver::CheckVersion(dep.VerReq, ver)) { warn_or_error(cmStrCat("Package '", dep.Name, "' version '", ver, "' does not meet version requirement '", dep.VerReq.string(), "' ", "of '", name, "'"), imEnv); return false; } continue; } inStack[dep.Name].emplace_back( pkgStackEntry{ std::move(dep.VerReq), name }); } return true; } struct PopulateArguments : CommonArguments { cm::optional Prefix; cm::optional>> 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 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; auto& imEnv = maybeEnv->second; 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 { imEnv.status.SetError(cmStrCat( "No '=' found in BIND_PC_REQUIRES argument '", provider_str, "'")); cmSystemTools::SetFatalErrorOccurred(); return { false, false }; } } } pkgStack inStack; std::unordered_map outStack; auto maybePackage = ImportPackage(*args.Package, args.Version, imEnv, pcEnv); if (!maybePackage) { return { !args.Required, false }; } imEnv.exact = false; if (!CheckPackageDependencies(*args.Package, prefix, *maybePackage, inStack, outStack, providers, imEnv)) { return { !args.Required, false }; } outStack[*args.Package] = std::move(*maybePackage); while (!inStack.empty()) { auto name = inStack.begin()->first; auto reqs = inStack.begin()->second; maybePackage = ImportPackage(name, reqs, imEnv, pcEnv); if (!maybePackage) { return { !args.Required, false }; } if (!CheckPackageDependencies(name, prefix, *maybePackage, inStack, outStack, providers, imEnv)) { return { !args.Required, false }; } inStack.erase(name); outStack[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 const& args, cmExecutionStatus& status) { std::vector 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, cmStateEnums::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 Name; }; auto const ImportParser = BIND_POPULATE(ImportArguments).Bind("NAME"_s, &ImportArguments::Name); bool HandleImportCommand(std::vector const& args, cmExecutionStatus& status) { std::vector 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, cmStateEnums::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, cmStateEnums::TargetType::INTERFACE_LIBRARY, false); tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", foreign_name); return true; } } // namespace bool cmCMakePkgConfigCommand(std::vector 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); }