PkgC: Add cmake_pkg_config(EXTRACT) command

* Wraps the llpkgc parser with cmPkgConfigParser
* Adds various resolution and mangling code under cmPkgConfigResolver
* Documents new command cmake_pkg_config(EXTRACT). Documentation is written with
  the assumption additional subcommands will be added soon.
* Adds various tests for the above
This commit is contained in:
Vito Gamberini
2024-07-22 10:34:46 -04:00
parent 35734c8de3
commit 8555c33d92
54 changed files with 2932 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
cmake_pkg_config
----------------
.. only:: html
.. contents::
Process pkg-config format package files.
Synopsis
^^^^^^^^
.. parsed-literal::
cmake_pkg_config(EXTRACT <package> [<version>] [...])
Introduction
^^^^^^^^^^^^
This command generates CMake variables and targets from pkg-config format
package files natively, without needing to invoke or even require the presence
of a pkg-config implementation. A ``<package>`` is either an absolute path to a
package file, or a package name to be searched for using the typical pkg-config
search patterns. The optional ``<version>`` string has the same format and
semantics as a pkg-config style version specifier, with the exception that if
no comparison operator is specified ``=`` is assumed.
.. _`common options`:
There are multiple signatures for this command, and some of the options are
common between them. They are:
``EXACT`` / ``QUIET`` / ``REQUIRED``
The ``EXACT`` option requests that the version string be matched exactly
(including empty string, if no version is provided), overriding the typical
pkg-config version comparison algorithm. This will ignore any comparison
operator attached to the version string.
The ``QUIET`` option disables informational messages, including those
indicating that the package cannot be found if it is not ``REQUIRED``. The
``REQUIRED`` option stops processing with an error message if the package
cannot be found.
``STRICTNESS <mode>``
Specify how strictly the contents of the package files will be verified during
parsing and resolution. An invalid file, under the provided strictness mode,
will cause the command to fail. Possible modes are:
* ``STRICT``: Closely mirrors the behavior of the original FDO pkg-config.
Variables and keywords must be unique. Variables must be defined before
they are used. The Name, Description, and Version keywords must be present.
The overall structure of the file must be valid and parsable.
* ``PERMISSIVE``: Closely mirrors the behavior of the pkgconf implementation.
Duplicate variables are overridden. Duplicate keywords are appended.
Undefined variables resolve to empty strings. The Name, Description, and
Version keywords must be present. The overall structure of the file must be
valid and parsable.
* ``BEST_EFFORT``: Same behavior as ``PERMISSIVE`` with regards to duplicate
or uninitialized variables and keywords, but will not fail under any
conditions. Package files which require BEST_EFFORT will fail validation
under all other major implementations and should be fixed.
The default strictness is ``PERMISSIVE``.
``ENV_MODE``
Specifies which environment variables will be queried when running a given
command. Possible modes are:
* ``FDO``: Queries only the original set of ``PKG_CONFIG_*`` environment
variables used by the freedesktop.org ``pkg-config`` implementation.
* ``PKGCONF``: Queries the more extensive set of environment variables used
by the ``pkgconf`` implementation.
* ``IGNORE``: Ignores the presence, absence, and value of environment
variables entirely. In all cases an environment variable would be queried
its treated as defined, but with a value of empty string for the purpose
of the operation. This does not modify the current environment. For boolean
environment variables, such as ``PKG_CONFIG_ALLOW_*``, this means they are
evaluated as truthy.
``PKG_CONFIG_SYSROOT_PATH`` is a minor exception. When ``ENV_MODE IGNORE``
is used, no root path prepending will occur by default and ``pc_sysrootdir``
remains defaulted to ``/``.
Target-generating subcommands always ignore flag-filtering environment
variables. The default environment mode is ``PKGCONF``.
``PC_LIBDIR <path>...``
Overrides the default search location for package files; also used to derive
the ``pc_path`` package variable.
When this option is not provided, the default library directory is the first
available of the following values:
#. ``CMAKE_PKG_CONFIG_PC_LIB_DIRS``
#. The ``PKG_CONFIG_LIBDIR`` environment variable
#. The output of ``pkg-config --variable pc_path pkg-config``
#. A platform-dependent default value
``PC_PATH <path>...``
Overrides the supplemental package file directories which will be prepended
to the search path; also used to derive the ``pc_path`` package variable.
When this option is not provided, the default paths are the first available of
the following values:
#. ``CMAKE_PKG_CONFIG_PC_PATH``
#. The ``PKG_CONFIG_PATH`` environment variable
#. Empty list
``DISABLE_UNINSTALLED <bool>``
Overrides the search behavior for "uninstalled" package files. These are
package files with an "-uninstalled" suffix which describe packages integrated
directly from a build tree.
Normally such package files have higher priority than "installed" packages.
When ``DISABLE_UNINSTALLED`` is true, searching for "uninstalled" packages
is disabled.
When this option is not provided, the default search behavior is determined
by the first available of the following values:
#. ``CMAKE_PKG_CONFIG_DISABLE_UNINSTALLED``
#. If the ``PKG_CONFIG_DISABLE_UNINSTALLED`` environment variable is defined
the search is disabled, otherwise it is enabled.
``PC_SYSROOT_DIR <path>``
Overrides the root path which will be prepended to paths specified by ``-I``
compile flags and ``-L`` library search locations; also used to derive the
``pc_sysrootdir`` package variable.
When this option is not provided, the default root path is provided by the
first available of the following values:
#. ``CMAKE_PKG_CONFIG_SYSROOT_DIR``
#. The ``PKG_CONFIG_SYSROOT_DIR`` environment variable
#. If no root path is available, nothing will be prepended to include or
library directory paths and ``pc_sysrootdir`` will be set to ``/``
``TOP_BUILD_DIR <path>``
Overrides the top build directory path used to derived the ``pc_top_builddir``
package variable.
When this option is not provided, the default top build directory path is
the first available of the following values:
#. ``CMAKE_PKG_CONFIG_TOP_BUILD_DIR``
#. The ``PKG_CONFIG_TOP_BUILD_DIR`` environment variable
#. If no top build directory path is available, the ``pc_top_builddir``
package variable is not set
Signatures
^^^^^^^^^^
.. signature::
cmake_pkg_config(EXTRACT <package> [<version>] [...])
Extract the contents of the package into variables.
.. code-block:: cmake
cmake_pkg_config(EXTRACT <package> [<version>]
[REQUIRED] [EXACT] [QUIET]
[STRICTNESS <mode>]
[ENV_MODE <mode>]
[PC_LIBDIR <path>...]
[PC_PATH <path>...]
[DISABLE_UNINSTALLED <bool>]
[PC_SYSROOT_DIR <path>]
[TOP_BUILD_DIR <path>]
[SYSTEM_INCLUDE_DIRS <path>...]
[SYSTEM_LIBRARY_DIRS <path>...]
[ALLOW_SYSTEM_INCLUDES <bool>]
[ALLOW_SYSTEM_LIBS <bool>])
The following variables will be populated from the contents of package file:
==================================== ====== ========================================================================================
Variable Type Definition
==================================== ====== ========================================================================================
``CMAKE_PKG_CONFIG_NAME`` String Value of the ``Name`` keyword
``CMAKE_PKG_CONFIG_DESCRIPTION`` String Value of the ``Description`` keyword
``CMAKE_PKG_CONFIG_VERSION`` String Value of the ``Version`` keyword
``CMAKE_PKG_CONFIG_PROVIDES`` List Value of the ``Provides`` keyword
``CMAKE_PKG_CONFIG_REQUIRES`` List Value of the ``Requires`` keyword
``CMAKE_PKG_CONFIG_CONFLICTS`` List Value of the ``Conflicts`` keyword
``CMAKE_PKG_CONFIG_CFLAGS`` String Value of the ``CFlags`` / ``Cflags`` keyword
``CMAKE_PKG_CONFIG_INCLUDES`` List All ``-I`` prefixed flags from ``CMAKE_PKG_CONFIG_CFLAGS``
``CMAKE_PKG_CONFIG_COMPILE_OPTIONS`` List All flags not prefixed with ``-I`` from ``CMAKE_PKG_CONFIG_CFLAGS``
``CMAKE_PKG_CONFIG_LIBS`` String Value of the ``Libs`` keyword
``CMAKE_PKG_CONFIG_LIBDIRS`` List All ``-L`` prefixed flags from ``CMAKE_PKG_CONFIG_LIBS``
``CMAKE_PKG_CONFIG_LIBNAMES`` List All ``-l`` prefixed flags from ``CMAKE_PKG_CONFIG_LIBS``
``CMAKE_PKG_CONFIG_LINK_OPTIONS`` List All flags not prefixed with ``-L`` or ``-l`` from ``CMAKE_PKG_CONFIG_LIBS``
``CMAKE_PKG_CONFIG_*_PRIVATE`` \* ``CFLAGS`` / ``LIBS`` / ``REQUIRES`` and derived, but in their ``.private`` suffix forms
==================================== ====== ========================================================================================
``SYSTEM_INCLUDE_DIRS``
Overrides the "system" directories for the purpose of flag mangling include
directories in ``CMAKE_PKG_CONFIG_CFLAGS`` and derived variables.
When this option is not provided, the default directories are provided by the
first available of the following values:
#. ``CMAKE_PKG_CONFIG_SYS_INCLUDE_DIRS``
#. The ``PKG_CONFIG_SYSTEM_INCLUDE_PATH`` environment variable
#. The output of ``pkgconf --variable pc_system_includedirs pkg-config``
#. A platform-dependent default value
Additionally, when the ``ENV_MODE`` is ``PKGCONF`` the
``CMAKE_PKG_CONFIG_PKGCONF_INCLUDES`` variable will be concatenated to the
list if available. If it is not available, the following environment variables
will be queried and concatenated:
* ``CPATH``
* ``C_INCLUDE_PATH``
* ``CPLUS_INCLUDE_PATH``
* ``OBJC_INCLUDE_PATH``
* ``INCLUDE`` (Windows Only)
``SYSTEM_LIBRARY_DIRS``
Overrides the "system" directories for the purpose of flag mangling library
directories in ``CMAKE_PKG_CONFIG_LIBS`` and derived variables.
When this option is not provided, the default directories are provided by the
first available of the following values:
#. ``CMAKE_PKG_CONFIG_SYS_LIB_DIRS``
#. The ``PKG_CONFIG_SYSTEM_LIBRARY_PATH`` environment variable
#. The output of ``pkgconf --variable pc_system_libdirs pkg-config``
#. A platform-dependent default value
Additionally, when the ``ENV_MODE`` is ``PKGCONF`` the
``CMAKE_PKG_CONFIG_PKGCONF_LIB_DIRS`` variable will be concatenated to the
list if available. If it is not available, the ``LIBRARY_PATH`` environment
variable will be queried and concatenated.
``ALLOW_SYSTEM_INCLUDES``
Preserves "system" directories during flag mangling of include directories
in ``CMAKE_PKG_CONFIG_CFLAGS`` and derived variables.
When this option is not provided, the default value is determined by the first
available of the following values:
#. ``CMAKE_PKG_CONFIG_ALLOW_SYS_INCLUDES``
#. If the ``PKG_CONFIG_ALLOW_SYSTEM_CFLAGS`` environment variable is defined
the flags are preserved, otherwise they are filtered during flag mangling.
``ALLOW_SYSTEM_LIBS``
Preserves "system" directories during flag mangling of library directories
in ``CMAKE_PKG_CONFIG_LIBS`` and derived variables.
When this option is not provided, the default value is determined by the first
available of the following values:
#. ``CMAKE_PKG_CONFIG_ALLOW_SYS_LIBS``
#. If the ``PKG_CONFIG_ALLOW_SYSTEM_LIBS`` environment variable is defined
the flags are preserved, otherwise they are filtered during flag mangling.

View File

@@ -22,6 +22,7 @@ These commands are always available.
/command/cmake_minimum_required
/command/cmake_parse_arguments
/command/cmake_path
/command/cmake_pkg_config
/command/cmake_policy
/command/configure_file
/command/continue

View File

@@ -373,6 +373,10 @@ add_library(
cmLocalCommonGenerator.h
cmLocalGenerator.cxx
cmLocalGenerator.h
cmPkgConfigParser.cxx
cmPkgConfigParser.h
cmPkgConfigResolver.cxx
cmPkgConfigResolver.h
cmPlaceholderExpander.cxx
cmPlaceholderExpander.h
cmRulePlaceholderExpander.cxx
@@ -537,6 +541,8 @@ add_library(
cmCMakeMinimumRequired.h
cmCMakePathCommand.h
cmCMakePathCommand.cxx
cmCMakePkgConfigCommand.h
cmCMakePkgConfigCommand.cxx
cmCMakePolicyCommand.cxx
cmCMakePolicyCommand.h
cmConditionEvaluator.cxx

View File

@@ -0,0 +1,722 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmCMakePkgConfigCommand.h"
#include <cstdio>
#include <memory>
#include <string>
#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 "cmValue.h"
#include <cmllpkgc/llpkgc.h>
// IWYU wants this
namespace {
struct ExtractArguments;
}
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, const char* cachevar,
const char* envvar, const char* desc,
const char* 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 = [&](const char* 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<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, const char* cachevar,
const char* envvar, const char* 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, const char* cachevar, const char* envvar,
const char* 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);
}
cm::optional<cmPkgConfigResult> HandleCommon(CommonArguments& args,
cmExecutionStatus& status)
{
auto& mf = status.GetMakefile();
if (!args.CheckArgs(status)) {
return {};
}
auto warn_or_error = [&](const std::string& err) {
if (args.Required) {
status.SetError(err);
cmSystemTools::SetFatalErrorOccurred();
} else if (!args.Quiet) {
mf.IssueMessage(MessageType::WARNING, err);
}
};
cm::filesystem::path path{ *args.Package };
cmPkgConfigEnv env;
if (args.PcLibdir) {
env.LibDirs = std::move(*args.PcLibdir);
}
if (args.PcPath) {
env.Path = std::move(*args.PcPath);
}
if (args.DisableUninstalled) {
env.DisableUninstalled = args.DisableUninstalled;
}
if (args.SysrootDir) {
env.SysrootDir = std::move(*args.SysrootDir);
}
if (args.TopBuildDir) {
env.TopBuildDir = std::move(*args.TopBuildDir);
}
CollectEnv(mf, env, args.EnvMode);
if (path.extension() == ".pc") {
if (!cmSystemTools::FileExists(path.string())) {
warn_or_error(cmStrCat("Could not find '", *args.Package, "'"));
return {};
}
} else {
std::vector<std::string> search;
if (env.Path) {
search = *env.Path;
if (env.LibDirs) {
search += *env.LibDirs;
}
} else if (env.LibDirs) {
search = *env.LibDirs;
}
if (env.DisableUninstalled && !*env.DisableUninstalled) {
auto uninstalled = path;
uninstalled.concat("-uninstalled.pc");
uninstalled =
cmSystemTools::FindFile(uninstalled.string(), search, true);
if (uninstalled.empty()) {
path =
cmSystemTools::FindFile(path.concat(".pc").string(), search, true);
if (path.empty()) {
warn_or_error(cmStrCat("Could not find '", *args.Package, "'"));
return {};
}
} else {
path = uninstalled;
}
} else {
path =
cmSystemTools::FindFile(path.concat(".pc").string(), search, true);
if (path.empty()) {
warn_or_error(cmStrCat("Could not find '", *args.Package, "'"));
return {};
}
}
}
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(), "'"));
return {};
}
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(), "'"));
return {};
}
using StrictnessType = CommonArguments::StrictnessType;
cmPkgConfigParser parser;
auto err = parser.Finish(buf.get(), len);
if (args.Strictness != StrictnessType::STRICTNESS_BEST_EFFORT &&
err != PCE_OK) {
warn_or_error(cmStrCat("Parsing failed for file '", path.string(), "'"));
return {};
}
cm::optional<cmPkgConfigResult> result;
if (args.Strictness == StrictnessType::STRICTNESS_STRICT) {
result = cmPkgConfigResolver::ResolveStrict(parser.Data(), std::move(env));
} else if (args.Strictness == StrictnessType::STRICTNESS_PERMISSIVE) {
result =
cmPkgConfigResolver::ResolvePermissive(parser.Data(), std::move(env));
} else {
result =
cmPkgConfigResolver::ResolveBestEffort(parser.Data(), std::move(env));
}
if (!result) {
warn_or_error(
cmStrCat("Resolution failed for file '", path.string(), "'"));
} else if (args.Exact) {
std::string ver;
if (args.Version) {
ver = cmPkgConfigResolver::ParseVersion(*args.Version).Version;
}
if (ver != result->Version()) {
warn_or_error(
cmStrCat("Package '", *args.Package, "' version '", result->Version(),
"' does not meet exact version requirement '", ver, "'"));
return {};
}
} else if (args.Version) {
auto rv = cmPkgConfigResolver::ParseVersion(*args.Version);
if (!cmPkgConfigResolver::CheckVersion(rv, result->Version())) {
warn_or_error(
cmStrCat("Package '", *args.Package, "' version '", result->Version(),
"' does not meet version requirement '", *args.Version, "'"));
return {};
}
}
return result;
}
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;
};
const auto 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 maybeResolved = HandleCommon(parsedArgs, status);
if (!maybeResolved) {
return !parsedArgs.Required;
}
auto& resolved = *maybeResolved;
auto version = resolved.Version();
if (parsedArgs.AllowSystemIncludes) {
resolved.env.AllowSysCflags = *parsedArgs.AllowSystemIncludes;
}
if (parsedArgs.AllowSystemLibs) {
resolved.env.AllowSysLibs = *parsedArgs.AllowSystemLibs;
}
if (parsedArgs.SystemIncludeDirs) {
resolved.env.SysCflags = *parsedArgs.SystemIncludeDirs;
}
if (parsedArgs.SystemLibraryDirs) {
resolved.env.SysLibs = *parsedArgs.SystemLibraryDirs;
}
auto& mf = status.GetMakefile();
mf.AddDefinition("CMAKE_PKG_CONFIG_NAME", resolved.Name());
mf.AddDefinition("CMAKE_PKG_CONFIG_DESCRIPTION", resolved.Description());
mf.AddDefinition("CMAKE_PKG_CONFIG_VERSION", version);
auto make_list = [&](const char* def,
const std::vector<cmPkgConfigDependency>& deps) {
std::vector<cm::string_view> vec;
vec.reserve(deps.size());
for (const auto& dep : deps) {
vec.emplace_back(dep.Name);
}
mf.AddDefinition(def, cmList::to_string(vec));
};
make_list("CMAKE_PKG_CONFIG_CONFLICTS", resolved.Conflicts());
make_list("CMAKE_PKG_CONFIG_PROVIDES", resolved.Provides());
make_list("CMAKE_PKG_CONFIG_REQUIRES", resolved.Requires());
make_list("CMAKE_PKG_CONFIG_REQUIRES_PRIVATE", resolved.Requires(true));
auto cflags = resolved.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 = resolved.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 = resolved.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 = resolved.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;
}
} // namespace
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 },
};
return subcommand(args[0], args, status);
}

View File

@@ -0,0 +1,13 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <string>
#include <vector>
class cmExecutionStatus;
bool cmCMakePkgConfigCommand(std::vector<std::string> const& args,
cmExecutionStatus& status);

View File

@@ -94,6 +94,7 @@
# include "cmAuxSourceDirectoryCommand.h"
# include "cmBuildNameCommand.h"
# include "cmCMakeHostSystemInformationCommand.h"
# include "cmCMakePkgConfigCommand.h"
# include "cmExportCommand.h"
# include "cmExportLibraryDependenciesCommand.h"
# include "cmFLTKWrapUICommand.h"
@@ -208,6 +209,7 @@ void GetScriptingCommands(cmState* state)
#if !defined(CMAKE_BOOTSTRAP)
state->AddBuiltinCommand("cmake_host_system_information",
cmCMakeHostSystemInformationCommand);
state->AddBuiltinCommand("cmake_pkg_config", cmCMakePkgConfigCommand);
state->AddBuiltinCommand("load_cache", cmLoadCacheCommand);
state->AddBuiltinCommand("remove", cmRemoveCommand);
state->AddBuiltinCommand("variable_watch", cmVariableWatchCommand);

View File

@@ -0,0 +1,151 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmPkgConfigParser.h"
#include <cstddef>
#include <vector>
#include <cm/string_view>
#include <cmllpkgc/llpkgc.h>
cmPkgConfigValueElement::cmPkgConfigValueElement(bool isVariable,
cm::string_view data)
: IsVariable{ isVariable }
, Data{ data }
{
}
cmPkgConfigEntry::cmPkgConfigEntry(bool isVariable, cm::string_view key)
: IsVariable{ isVariable }
, Key{ key }
{
}
cmPkgConfigParser::cmPkgConfigParser()
{
llpkgc_init(static_cast<llpkgc_t*>(this), &Settings_);
}
llpkgc_errno_t cmPkgConfigParser::Parse(char* buf, std::size_t len)
{
return llpkgc_execute(static_cast<llpkgc_t*>(this), buf, len);
}
llpkgc_errno_t cmPkgConfigParser::Finish()
{
return llpkgc_finish(static_cast<llpkgc_t*>(this));
}
llpkgc_errno_t cmPkgConfigParser::Finish(char* buf, std::size_t len)
{
Parse(buf, len);
return llpkgc_finish(static_cast<llpkgc_t*>(this));
}
int cmPkgConfigParser::OnSpanNext(const char*, std::size_t len)
{
Len_ += len;
return 0;
}
int cmPkgConfigParser::OnSpanNextTr(llpkgc_t* parser, const char* at,
std::size_t len)
{
return static_cast<cmPkgConfigParser*>(parser)->OnSpanNext(at, len);
}
int cmPkgConfigParser::OnKey(const char* at, std::size_t len)
{
Ptr_ = at;
Len_ = len;
Settings_.on_key = OnSpanNextTr;
return 0;
}
int cmPkgConfigParser::OnKeyTr(llpkgc_t* parser, const char* at,
std::size_t len)
{
return static_cast<cmPkgConfigParser*>(parser)->OnKey(at, len);
}
int cmPkgConfigParser::OnKeywordComplete()
{
Data_.emplace_back(false, cm::string_view{ Ptr_, Len_ });
Settings_.on_key = OnKeyTr;
return 0;
}
int cmPkgConfigParser::OnKeywordCompleteTr(llpkgc_t* parser)
{
return static_cast<cmPkgConfigParser*>(parser)->OnKeywordComplete();
}
int cmPkgConfigParser::OnVariableComplete()
{
Data_.emplace_back(true, cm::string_view{ Ptr_, Len_ });
Settings_.on_key = OnKeyTr;
return 0;
}
int cmPkgConfigParser::OnVariableCompleteTr(llpkgc_t* parser)
{
return static_cast<cmPkgConfigParser*>(parser)->OnVariableComplete();
}
int cmPkgConfigParser::OnValueLiteral(const char* at, std::size_t len)
{
Ptr_ = at;
Len_ = len;
Settings_.on_value_literal = OnSpanNextTr;
return 0;
}
int cmPkgConfigParser::OnValueLiteralTr(llpkgc_t* parser, const char* at,
std::size_t len)
{
return static_cast<cmPkgConfigParser*>(parser)->OnValueLiteral(at, len);
}
int cmPkgConfigParser::OnValueLiteralComplete()
{
Settings_.on_value_literal = OnValueLiteralTr;
if (Len_) {
Data_.back().Val.emplace_back(false, cm::string_view{ Ptr_, Len_ });
}
return 0;
}
int cmPkgConfigParser::OnValueLiteralCompleteTr(llpkgc_t* parser)
{
return static_cast<cmPkgConfigParser*>(parser)->OnValueLiteralComplete();
}
int cmPkgConfigParser::OnValueVariable(const char* at, std::size_t len)
{
Ptr_ = at;
Len_ = len;
Settings_.on_value_variable = OnSpanNextTr;
return 0;
}
int cmPkgConfigParser::OnValueVariableTr(llpkgc_t* parser, const char* at,
std::size_t len)
{
return static_cast<cmPkgConfigParser*>(parser)->OnValueVariable(at, len);
}
int cmPkgConfigParser::OnValueVariableComplete()
{
Settings_.on_value_variable = OnValueVariableTr;
Data_.back().Val.emplace_back(true, cm::string_view{ Ptr_, Len_ });
return 0;
}
int cmPkgConfigParser::OnValueVariableCompleteTr(llpkgc_t* parser)
{
return static_cast<cmPkgConfigParser*>(parser)->OnValueVariableComplete();
}

View File

@@ -0,0 +1,93 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <cstddef>
#include <vector>
#include <cm/string_view>
#include <cmllpkgc/llpkgc.h>
struct cmPkgConfigValueElement
{
cmPkgConfigValueElement() = default;
cmPkgConfigValueElement(bool isVariable, cm::string_view data);
bool IsVariable;
cm::string_view Data;
};
struct cmPkgConfigEntry
{
cmPkgConfigEntry() = default;
cmPkgConfigEntry(bool isVariable, cm::string_view key);
bool IsVariable;
cm::string_view Key;
std::vector<cmPkgConfigValueElement> Val;
};
class cmPkgConfigParser : llpkgc_t
{
public:
cmPkgConfigParser();
llpkgc_errno_t Parse(char* buf, std::size_t len);
llpkgc_errno_t Finish();
llpkgc_errno_t Finish(char* buf, std::size_t len);
std::vector<cmPkgConfigEntry>& Data() { return Data_; }
private:
int OnSpanNext(const char*, std::size_t len);
static int OnSpanNextTr(llpkgc_t* parser, const char* at, std::size_t len);
int OnKey(const char* at, std::size_t len);
static int OnKeyTr(llpkgc_t* parser, const char* at, std::size_t len);
int OnKeywordComplete();
static int OnKeywordCompleteTr(llpkgc_t* parser);
int OnVariableComplete();
static int OnVariableCompleteTr(llpkgc_t* parser);
int OnValueLiteral(const char* at, std::size_t len);
static int OnValueLiteralTr(llpkgc_t* parser, const char* at,
std::size_t len);
int OnValueLiteralComplete();
static int OnValueLiteralCompleteTr(llpkgc_t* parser);
int OnValueVariable(const char* at, std::size_t len);
static int OnValueVariableTr(llpkgc_t* parser, const char* at,
std::size_t len);
int OnValueVariableComplete();
static int OnValueVariableCompleteTr(llpkgc_t* parser);
llpkgc_settings_t Settings_{
OnKeyTr,
OnValueLiteralTr,
OnValueVariableTr,
nullptr, // on_line_begin
OnKeywordCompleteTr,
OnVariableCompleteTr,
OnValueLiteralCompleteTr,
OnValueVariableCompleteTr,
nullptr, // on_value_complete
nullptr, // on_pkgc_complete
};
const char* Ptr_;
std::size_t Len_;
std::vector<cmPkgConfigEntry> Data_;
};

View File

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

View File

@@ -0,0 +1,172 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include "cmConfigure.h" // IWYU pragma: keep
#include <string>
#include <unordered_map>
#include <vector>
#include <cm/optional>
#include <cm/string_view>
// From cmPkgConfigParser.h, IWYU doesn't like including the header
struct cmPkgConfigEntry;
struct cmPkgConfigCflagsResult
{
std::string Flagline;
std::vector<std::string> Includes;
std::vector<std::string> CompileOptions;
};
struct cmPkgConfigLibsResult
{
std::string Flagline;
std::vector<std::string> LibDirs;
std::vector<std::string> LibNames;
std::vector<std::string> LinkOptions;
};
struct cmPkgConfigVersionReq
{
enum
{
ANY = 0,
LT,
LT_EQ,
EQ,
NEQ,
GT_EQ,
GT,
} Operation = ANY;
std::string Version;
};
struct cmPkgConfigDependency
{
std::string Name;
cmPkgConfigVersionReq VerReq;
};
struct cmPkgConfigEnv
{
cm::optional<std::vector<std::string>> Path;
cm::optional<std::vector<std::string>> LibDirs;
cm::optional<std::vector<std::string>> SysCflags;
cm::optional<std::vector<std::string>> SysLibs;
cm::optional<std::string> SysrootDir;
cm::optional<std::string> TopBuildDir;
cm::optional<bool> DisableUninstalled;
bool AllowSysCflags = true;
bool AllowSysLibs = true;
};
class cmPkgConfigResult
{
public:
std::unordered_map<std::string, std::string> Keywords;
std::unordered_map<std::string, std::string> Variables;
std::string Name();
std::string Description();
std::string Version();
std::vector<cmPkgConfigDependency> Conflicts();
std::vector<cmPkgConfigDependency> Provides();
std::vector<cmPkgConfigDependency> Requires(bool priv = false);
cmPkgConfigCflagsResult Cflags(bool priv = false);
cmPkgConfigLibsResult Libs(bool priv = false);
cmPkgConfigEnv env;
private:
std::string StrOrDefault(const std::string& key, cm::string_view def = "");
};
class cmPkgConfigResolver
{
friend class cmPkgConfigResult;
public:
static cm::optional<cmPkgConfigResult> ResolveStrict(
const std::vector<cmPkgConfigEntry>& entries, cmPkgConfigEnv env);
static cm::optional<cmPkgConfigResult> ResolvePermissive(
const std::vector<cmPkgConfigEntry>& entries, cmPkgConfigEnv env);
static cmPkgConfigResult ResolveBestEffort(
const std::vector<cmPkgConfigEntry>& entries, cmPkgConfigEnv env);
static cmPkgConfigVersionReq ParseVersion(const std::string& version);
static bool CheckVersion(const cmPkgConfigVersionReq& desired,
const std::string& provided);
static void ReplaceSep(std::string& list);
#ifdef _WIN32
static const char Sep = ';';
#else
static const char Sep = ':';
#endif
private:
static std::string HandleVariablePermissive(
const cmPkgConfigEntry& entry,
const std::unordered_map<std::string, std::string>& variables);
static cm::optional<std::string> HandleVariableStrict(
const cmPkgConfigEntry& entry,
const std::unordered_map<std::string, std::string>& variables);
static std::string HandleKeyword(
const cmPkgConfigEntry& entry,
const std::unordered_map<std::string, std::string>& variables);
static std::vector<cm::string_view> TokenizeFlags(
const std::string& flagline);
static cmPkgConfigCflagsResult MangleCflags(
const std::vector<cm::string_view>& flags);
static cmPkgConfigCflagsResult MangleCflags(
const std::vector<cm::string_view>& flags, const std::string& sysroot);
static cmPkgConfigCflagsResult MangleCflags(
const std::vector<cm::string_view>& flags,
const std::vector<std::string>& syspaths);
static cmPkgConfigCflagsResult MangleCflags(
const std::vector<cm::string_view>& flags, const std::string& sysroot,
const std::vector<std::string>& syspaths);
static cmPkgConfigLibsResult MangleLibs(
const std::vector<cm::string_view>& flags);
static cmPkgConfigLibsResult MangleLibs(
const std::vector<cm::string_view>& flags, const std::string& sysroot);
static cmPkgConfigLibsResult MangleLibs(
const std::vector<cm::string_view>& flags,
const std::vector<std::string>& syspaths);
static cmPkgConfigLibsResult MangleLibs(
const std::vector<cm::string_view>& flags, const std::string& sysroot,
const std::vector<std::string>& syspaths);
static std::string Reroot(cm::string_view flag, cm::string_view prefix,
const std::string& sysroot);
static cmPkgConfigVersionReq ParseVersion(std::string::const_iterator& cur,
std::string::const_iterator end);
static std::vector<cmPkgConfigDependency> ParseDependencies(
const std::string& deps);
};

View File

@@ -560,6 +560,7 @@ add_RunCMake_test(cmake_language)
add_RunCMake_test(cmake_minimum_required)
add_RunCMake_test(cmake_parse_arguments)
add_RunCMake_test(cmake_path -DMSYS=${MSYS})
add_RunCMake_test(cmake_pkg_config)
add_RunCMake_test(continue)
add_executable(color_warning color_warning.c)
add_executable(fake_build_command fake_build_command.c)

View File

@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.30)
project(${RunCMake_TEST} NONE)
set(CMAKE_PKG_CONFIG_PC_LIB_DIRS ${CMAKE_CURRENT_LIST_DIR}/PackageRoot)
include(${RunCMake_TEST}.cmake)

View File

@@ -0,0 +1,3 @@
Name:
Version: aa
Description:

View File

@@ -0,0 +1,3 @@
Name:
Version: 11
Description:

View File

@@ -0,0 +1,3 @@
Name:
Version: 1.1.1
Description:

View File

@@ -0,0 +1,3 @@
Name:
Version: ~0
Description:

View File

@@ -0,0 +1,3 @@
Name:
Version: ~~1
Description:

View File

@@ -0,0 +1,3 @@
Name:
Version: 01
Description:

View File

@@ -0,0 +1,15 @@
Name: Extract All
Description: All flags example
Version: 1.0.0
Conflicts: Alpha Beta
Provides: Gamma Delta
Requires: Epsilon Zea
Requires.private: Eta Theta
Cflags: Iota -IKappa Lambda -IMu
Cflags.private: Nu -IXi Omnicron -IPi
Libs: Rho -LSigma -lTau Upsilon -LPhi -lChi
Libs.private: Psi -LOmega -lMoe Larry -LCurly -lShemp

View File

@@ -0,0 +1,3 @@
Name: Bar
Description: Bar Description
Version: 1.0.0

View File

@@ -0,0 +1,3 @@
Name: Baz
Description: Baz Description
Version: 1.0.0

View File

@@ -0,0 +1,6 @@
Name: Cflags Bothcase
Description: The f is lowercase and uppercase
Version: 1.0.0
Cflags: lowercase
CFlags: uppercase

View File

@@ -0,0 +1,5 @@
Name: Cflags Lowercase
Description: The f is lowercase
Version: 1.0.0
Cflags: lowercase

View File

@@ -0,0 +1,5 @@
Name: CFlags Uppercase
Description: The f is uppercase
Version: 1.0.0
CFlags: uppercase

View File

@@ -0,0 +1,3 @@
Name:
Description:
Version:

View File

@@ -0,0 +1,3 @@
Name: Foo
Description: Foo Description
Version: 1.0.0

View File

@@ -0,0 +1,4 @@
Name: Invalid
Description: Will cause a parse error
Version: 1.0.0
BrokenKey

View File

@@ -0,0 +1,2 @@
Name: name
Version: version

View File

@@ -0,0 +1,2 @@
Description: description
Version: version

View File

@@ -0,0 +1,2 @@
Name: name
Description: description

View File

@@ -0,0 +1,5 @@
Name: Qux
Description: Qux Description
Version: 1.0.0
Cflags: QuxUninstalled

View File

@@ -0,0 +1,5 @@
Name: Qux
Description: Qux Description
Version: 1.0.0
Cflags: QuxInstalled

View File

@@ -0,0 +1,6 @@
Name: Relocate
Description: For testing relocation and flag mangling
Version: 1.0.0
Cflags: -I/Alpha Beta -I/Gamma
Libs: -L/Delta Epsilon -L/Zeta

View File

@@ -0,0 +1,18 @@
include(RunCMake)
set(cmd ${CMAKE_COMMAND} ${CMAKE_CURRENT_LIST_DIR} -G ${RunCMake_GENERATOR})
foreach(strictness IN ITEMS STRICT PERMISSIVE BEST_EFFORT)
run_cmake_command(TestStrictness-${strictness} ${cmd}
-DRunCMake_TEST=TestStrictness -DSTRICTNESS=${strictness}
)
endforeach()
run_cmake(TestEnv)
run_cmake(TestExtract)
run_cmake(TestMangle)
run_cmake(TestQuiet)
run_cmake(TestRequired)
run_cmake(TestReroot)
run_cmake(TestUninstalled)
run_cmake(TestVersion)

View File

@@ -0,0 +1,15 @@
Includes: -I/Alpha;-I/Gamma
LibDirs: -L/Delta;-L/Zeta
Cflags: QuxInstalled
PC_LIB_DIRS: Alpha;Beta
PC_PATH: [^
]*/PackageRoot
DISABLE_UNINSTALLED: ON
SYSROOT_DIR: Delta
TOP_BUILD_DIR: Epsilon
SYSTEM_INCLUDE_DIRS: Zeta;Eta
SYSTEM_LIB_DIRS: Theta;Iota
ALLOW_SYSTEM_INCLUDES: ON
ALLOW_SYSTEM_LIBRARIES: ON
PKGCONF_INCLUDES: Mu;Nu;Xi;Omnicron;Pi;Rho;Sigma;Tau
PKGCONF_LIB_DIRS: Upsilon;Phi

View File

@@ -0,0 +1,75 @@
set(CMAKE_PKG_CONFIG_PC_LIB_DIRS)
set(ENV{PKG_CONFIG_PATH} ${CMAKE_CURRENT_LIST_DIR}/PackageRoot)
if(WIN32)
set(sep ";")
else()
set(sep ":")
endif()
set(ENV{PKG_CONFIG_LIBDIR} "Alpha${sep}Beta")
set(ENV{PKG_CONFIG_DISABLE_UNINSTALLED} Gamma)
set(ENV{PKG_CONFIG_SYSROOT_DIR} Delta)
set(ENV{PKG_CONFIG_TOP_BUILD_DIR} Epsilon)
set(ENV{PKG_CONFIG_SYSTEM_INCLUDE_PATH} "Zeta${sep}Eta")
set(ENV{PKG_CONFIG_SYSTEM_LIBRARY_PATH} "Theta${sep}Iota")
set(ENV{PKG_CONFIG_ALLOW_SYSTEM_CFLAGS} Kappa)
set(ENV{PKG_CONFIG_ALLOW_SYSTEM_LIBS} Lambda)
set(ENV{CPATH} "Mu${sep}Nu")
set(ENV{C_INCLUDE_PATH} "Xi${sep}Omnicron")
set(ENV{CPLUS_INCLUDE_PATH} "Pi${sep}Rho")
if(WIN32)
set(ENV{OBJC_INCLUDE_PATH} Sigma)
set(ENV{INCLUDE} Tau)
else()
set(ENV{OBJC_INCLUDE_PATH} Sigma:Tau)
endif()
set(ENV{LIBRARY_PATH} "Upsilon${sep}Phi")
cmake_pkg_config(
EXTRACT relocate
ENV_MODE IGNORE
PC_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageRoot
SYSTEM_INCLUDE_DIRS /Alpha
SYSTEM_LIBRARY_DIRS /Beta
)
# Shouldn't mangle, ALLOW_SYSTEM_* should default to on under ENV IGNORE
message("Includes: ${CMAKE_PKG_CONFIG_INCLUDES}")
message("LibDirs: ${CMAKE_PKG_CONFIG_LIBDIRS}")
cmake_pkg_config(
EXTRACT qux
ENV_MODE IGNORE
PC_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageRoot
)
# Shouldn't find uninstalled package
message("Cflags: ${CMAKE_PKG_CONFIG_CFLAGS}")
cmake_pkg_config(
EXTRACT foo
ENV_MODE FDO
)
message("PC_LIB_DIRS: ${CMAKE_PKG_CONFIG_PC_LIB_DIRS}")
message("PC_PATH: ${CMAKE_PKG_CONFIG_PC_PATH}")
message("DISABLE_UNINSTALLED: ${CMAKE_PKG_CONFIG_DISABLE_UNINSTALLED}")
message("SYSROOT_DIR: ${CMAKE_PKG_CONFIG_SYSROOT_DIR}")
message("TOP_BUILD_DIR: ${CMAKE_PKG_CONFIG_TOP_BUILD_DIR}")
message("SYSTEM_INCLUDE_DIRS: ${CMAKE_PKG_CONFIG_SYS_INCLUDE_DIRS}")
message("SYSTEM_LIB_DIRS: ${CMAKE_PKG_CONFIG_SYS_LIB_DIRS}")
message("ALLOW_SYSTEM_INCLUDES: ${CMAKE_PKG_CONFIG_ALLOW_SYS_INCLUDES}")
message("ALLOW_SYSTEM_LIBRARIES: ${CMAKE_PKG_CONFIG_ALLOW_SYS_LIBS}")
cmake_pkg_config(
EXTRACT foo
ENV_MODE PKGCONF
)
message("PKGCONF_INCLUDES: ${CMAKE_PKG_CONFIG_PKGCONF_INCLUDES}")
message("PKGCONF_LIB_DIRS: ${CMAKE_PKG_CONFIG_PKGCONF_LIB_DIRS}")

View File

@@ -0,0 +1,21 @@
Name: Extract All
Description: All flags example
Version: 1.0.0
Conflicts: Alpha;Beta
Provides: Gamma;Delta
Requires: Epsilon;Zea
Requires.private: Eta;Theta
Cflags: Iota -IKappa Lambda -IMu
Includes: -IKappa;-IMu
CompileOptions: Iota;Lambda
Cflags.private: Nu -IXi Omnicron -IPi
Includes.private: -IXi;-IPi
CompileOptions.private: Nu;Omnicron
Libs: Rho -LSigma -lTau Upsilon -LPhi -lChi
LibDirs: -LSigma;-LPhi
LibNames: -lTau;-lChi
LinkOptions: Rho;Upsilon
Libs.private: Psi -LOmega -lMoe Larry -LCurly -lShemp
LibDirs.private: -LOmega;-LCurly
LibNames.private: -lMoe;-lShemp
LinkOptions.private: Psi;Larry

View File

@@ -0,0 +1,29 @@
cmake_pkg_config(EXTRACT all-extract-fields)
message("Name: ${CMAKE_PKG_CONFIG_NAME}")
message("Description: ${CMAKE_PKG_CONFIG_DESCRIPTION}")
message("Version: ${CMAKE_PKG_CONFIG_VERSION}")
message("Conflicts: ${CMAKE_PKG_CONFIG_CONFLICTS}")
message("Provides: ${CMAKE_PKG_CONFIG_PROVIDES}")
message("Requires: ${CMAKE_PKG_CONFIG_REQUIRES}")
message("Requires.private: ${CMAKE_PKG_CONFIG_REQUIRES_PRIVATE}")
message("Cflags: ${CMAKE_PKG_CONFIG_CFLAGS}")
message("Includes: ${CMAKE_PKG_CONFIG_INCLUDES}")
message("CompileOptions: ${CMAKE_PKG_CONFIG_COMPILE_OPTIONS}")
message("Cflags.private: ${CMAKE_PKG_CONFIG_CFLAGS_PRIVATE}")
message("Includes.private: ${CMAKE_PKG_CONFIG_INCLUDES_PRIVATE}")
message("CompileOptions.private: ${CMAKE_PKG_CONFIG_COMPILE_OPTIONS_PRIVATE}")
message("Libs: ${CMAKE_PKG_CONFIG_LIBS}")
message("LibDirs: ${CMAKE_PKG_CONFIG_LIBDIRS}")
message("LibNames: ${CMAKE_PKG_CONFIG_LIBNAMES}")
message("LinkOptions: ${CMAKE_PKG_CONFIG_LINK_OPTIONS}")
message("Libs.private: ${CMAKE_PKG_CONFIG_LIBS_PRIVATE}")
message("LibDirs.private: ${CMAKE_PKG_CONFIG_LIBDIRS_PRIVATE}")
message("LibNames.private: ${CMAKE_PKG_CONFIG_LIBNAMES_PRIVATE}")
message("LinkOptions.private: ${CMAKE_PKG_CONFIG_LINK_OPTIONS_PRIVATE}")

View File

@@ -0,0 +1,8 @@
Cflags: Beta -I/Gamma
Includes: -I/Gamma
Libs: Epsilon -L/Zeta
LibDirs: -L/Zeta
Cflags: -I/Alpha Beta -I/Gamma
Includes: -I/Alpha;-I/Gamma
Libs: -L/Delta Epsilon -L/Zeta
LibDirs: -L/Delta;-L/Zeta

View File

@@ -0,0 +1,22 @@
set(CMAKE_PKG_CONFIG_SYS_INCLUDE_DIRS /Alpha)
set(CMAKE_PKG_CONFIG_SYS_LIB_DIRS /Delta)
cmake_pkg_config(EXTRACT relocate)
message("Cflags: ${CMAKE_PKG_CONFIG_CFLAGS}")
message("Includes: ${CMAKE_PKG_CONFIG_INCLUDES}")
message("Libs: ${CMAKE_PKG_CONFIG_LIBS}")
message("LibDirs: ${CMAKE_PKG_CONFIG_LIBDIRS}")
cmake_pkg_config(
EXTRACT relocate
ALLOW_SYSTEM_INCLUDES ON
ALLOW_SYSTEM_LIBS ON
)
message("Cflags: ${CMAKE_PKG_CONFIG_CFLAGS}")
message("Includes: ${CMAKE_PKG_CONFIG_INCLUDES}")
message("Libs: ${CMAKE_PKG_CONFIG_LIBS}")
message("LibDirs: ${CMAKE_PKG_CONFIG_LIBDIRS}")

View File

@@ -0,0 +1,29 @@
cmake_pkg_config(
EXTRACT foo
QUIET
STRICTNESS STRICT
)
cmake_pkg_config(
EXTRACT no-name
QUIET
STRICTNESS STRICT
)
cmake_pkg_config(
EXTRACT empty-key
QUIET
STRICTNESS STRICT
)
cmake_pkg_config(
EXTRACT cflags-bothcase-f
QUIET
STRICTNESS STRICT
)
cmake_pkg_config(
EXTRACT does-not-exist
QUIET
STRICTNESS STRICT
)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,4 @@
CMake Error at TestRequired.cmake:[0-9]+ \(cmake_pkg_config\):
cmake_pkg_config Could not find 'does-not-exist'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@@ -0,0 +1,9 @@
cmake_pkg_config(
EXTRACT foo
REQUIRED
)
cmake_pkg_config(
EXTRACT does-not-exist
REQUIRED
)

View File

@@ -0,0 +1,4 @@
Cflags: -I/NewRoot/Alpha Beta -I/NewRoot/Gamma
Includes: -I/NewRoot/Alpha;-I/NewRoot/Gamma
Libs: -L/NewRoot/Delta Epsilon -L/NewRoot/Zeta
LibDirs: -L/NewRoot/Delta;-L/NewRoot/Zeta

View File

@@ -0,0 +1,10 @@
cmake_pkg_config(
EXTRACT relocate
PC_SYSROOT_DIR /NewRoot
)
message("Cflags: ${CMAKE_PKG_CONFIG_CFLAGS}")
message("Includes: ${CMAKE_PKG_CONFIG_INCLUDES}")
message("Libs: ${CMAKE_PKG_CONFIG_LIBS}")
message("LibDirs: ${CMAKE_PKG_CONFIG_LIBDIRS}")

View File

@@ -0,0 +1,3 @@
Cflags: lowercase
CFlags: uppercase
Cflags: lowercase uppercase

View File

@@ -0,0 +1,31 @@
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Resolution failed for file[^
]*(.)*/PackageRoot/no-name.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Resolution failed for file[^
]*(.)*/PackageRoot/no-description.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Resolution failed for file[^
]*(.)*/PackageRoot/no-version.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Parsing failed for file[^
]*(.)*/PackageRoot/invalid.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
Cflags: lowercase
CFlags: uppercase
Cflags: lowercase uppercase

View File

@@ -0,0 +1,38 @@
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Resolution failed for file[^
]*(.)*/PackageRoot/no-name.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Resolution failed for file[^
]*(.)*/PackageRoot/no-description.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Resolution failed for file[^
]*(.)*/PackageRoot/no-version.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Parsing failed for file[^
]*(.)*/PackageRoot/invalid.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
Cflags: lowercase
CFlags: uppercase
CMake Warning at TestStrictness.cmake:[0-9]+ \(cmake_pkg_config\):
Resolution failed for file[^
]*(.)*/PackageRoot/cflags-bothcase-f.pc'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
Cflags:

View File

@@ -0,0 +1,51 @@
cmake_pkg_config(
EXTRACT foo
STRICTNESS ${STRICTNESS}
REQUIRED
)
cmake_pkg_config(
EXTRACT empty-key
STRICTNESS ${STRICTNESS}
REQUIRED
)
cmake_pkg_config(
EXTRACT no-name
STRICTNESS ${STRICTNESS}
)
cmake_pkg_config(
EXTRACT no-description
STRICTNESS ${STRICTNESS}
)
cmake_pkg_config(
EXTRACT no-version
STRICTNESS ${STRICTNESS}
)
cmake_pkg_config(
EXTRACT invalid
STRICTNESS ${STRICTNESS}
)
cmake_pkg_config(
EXTRACT cflags-lowercase-f
STRICTNESS ${STRICTNESS}
)
message("Cflags: ${CMAKE_PKG_CONFIG_CFLAGS}")
set(CMAKE_PKG_CONFIG_CFLAGS)
cmake_pkg_config(
EXTRACT cflags-uppercase-f
STRICTNESS ${STRICTNESS}
)
message("CFlags: ${CMAKE_PKG_CONFIG_CFLAGS}")
set(CMAKE_PKG_CONFIG_CFLAGS)
cmake_pkg_config(
EXTRACT cflags-bothcase-f
STRICTNESS ${STRICTNESS}
)
message("Cflags: ${CMAKE_PKG_CONFIG_CFLAGS}")

View File

@@ -0,0 +1,2 @@
QuxUninstalled
QuxInstalled

View File

@@ -0,0 +1,10 @@
cmake_pkg_config(EXTRACT qux)
message(${CMAKE_PKG_CONFIG_CFLAGS})
cmake_pkg_config(
EXTRACT qux
DISABLE_UNINSTALLED ON
)
message(${CMAKE_PKG_CONFIG_CFLAGS})

View File

@@ -0,0 +1,103 @@
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'a' version 'aa' does not meet version requirement '<a'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'a' version 'aa' does not meet version requirement '>aaa'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'a' version 'aa' does not meet version requirement '>bb'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'a' version 'aa' does not meet version requirement '>1'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'empty-key' version '' does not meet version requirement '!='
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'empty-key' version '' does not meet version requirement '=0'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'one' version '11' does not meet version requirement '<1'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'one' version '11' does not meet version requirement '>111'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'one' version '11' does not meet version requirement '>22'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'one' version '11' does not meet version requirement '<a'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'onedot' version '1.1.1' does not meet version requirement '>1.2.1'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'onedot' version '1.1.1' does not meet version requirement '>
1.2.1'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'onedot' version '1.1.1' does not meet exact version requirement
'01.01.01'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'pseudo-empty' version '~0' does not meet version requirement '=~'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'pseudo-empty' version '~0' does not meet version requirement
'!=~0'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'tilde' version '~~1' does not meet version requirement '>~1'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)
CMake Warning at TestVersion.cmake:[0-9]+ \(cmake_pkg_config\):
Package 'tilde' version '~~1' does not meet version requirement '<~~~1'
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@@ -0,0 +1,65 @@
set(CMAKE_PKG_CONFIG_PC_PATH ${CMAKE_CURRENT_LIST_DIR}/PackageRoot/VersionPackages)
# Good = Should Succeed
# Bad = Should Warn
cmake_pkg_config(EXTRACT a =aa) # Good
cmake_pkg_config(EXTRACT a >a) # Good
cmake_pkg_config(EXTRACT a <a) # Bad
cmake_pkg_config(EXTRACT a >aaa) # Bad
cmake_pkg_config(EXTRACT a <aaa) # Good
cmake_pkg_config(EXTRACT a !=bb) # Good
cmake_pkg_config(EXTRACT a >bb) # Bad
cmake_pkg_config(EXTRACT a <bb) # Good
cmake_pkg_config(EXTRACT a >1) # Bad
cmake_pkg_config(EXTRACT a <1) # Good
cmake_pkg_config(EXTRACT empty-key =) # Good
cmake_pkg_config(EXTRACT empty-key !=) # Bad
cmake_pkg_config(EXTRACT empty-key =0) # Bad
cmake_pkg_config(EXTRACT empty-key !=0) # Good
cmake_pkg_config(EXTRACT empty-key EXACT) # Good
cmake_pkg_config(EXTRACT one =11) # Good
cmake_pkg_config(EXTRACT one >1) # Good
cmake_pkg_config(EXTRACT one <1) # Bad
cmake_pkg_config(EXTRACT one >111) # Bad
cmake_pkg_config(EXTRACT one <111) # Good
cmake_pkg_config(EXTRACT one !=22) # Good
cmake_pkg_config(EXTRACT one >22) # Bad
cmake_pkg_config(EXTRACT one <22) # Good
cmake_pkg_config(EXTRACT one >a) # Good
cmake_pkg_config(EXTRACT one <a) # Bad
cmake_pkg_config(EXTRACT onedot 1.1.1) # Good
cmake_pkg_config(EXTRACT onedot 01.01.01) # Good
cmake_pkg_config(EXTRACT onedot =1.1.1) # Good
cmake_pkg_config(EXTRACT onedot =01.01.01) # Good
cmake_pkg_config(EXTRACT onedot <1.2.1) # Good
cmake_pkg_config(EXTRACT onedot >1.2.1) # Bad
cmake_pkg_config(EXTRACT onedot "< 1.2.1") # Good
cmake_pkg_config(EXTRACT onedot "> 1.2.1") # Bad
cmake_pkg_config(EXTRACT onedot 1.1.1 EXACT) # Good
cmake_pkg_config(EXTRACT onedot =1.1.1 EXACT) # Good
cmake_pkg_config(EXTRACT onedot =01.01.01 EXACT) # Bad
cmake_pkg_config(EXTRACT pseudo-empty =~) # Bad
cmake_pkg_config(EXTRACT pseudo-empty !=~) # Good
cmake_pkg_config(EXTRACT pseudo-empty =~0) # Good
cmake_pkg_config(EXTRACT pseudo-empty !=~0) # Bad
cmake_pkg_config(EXTRACT tilde =~~1) # Good
cmake_pkg_config(EXTRACT tilde <~1) # Good
cmake_pkg_config(EXTRACT tilde >~1) # Bad
cmake_pkg_config(EXTRACT tilde <~~~1) # Bad
cmake_pkg_config(EXTRACT tilde >~~~1) # Good
cmake_pkg_config(EXTRACT zeroone =1) # Good
cmake_pkg_config(EXTRACT zeroone =001) # Good