summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVito Gamberini <vito.gamberini@kitware.com>2024-07-22 14:34:46 (GMT)
committerVito Gamberini <vito.gamberini@kitware.com>2024-07-29 17:14:46 (GMT)
commit8555c33d9237ae359f8d979af279af3809607a70 (patch)
treea2f0f778ada30d4eca4216f0c97b3782a88c4177
parent35734c8de3013c9e4bd2b2693640d2bacd39e49c (diff)
downloadCMake-8555c33d9237ae359f8d979af279af3809607a70.zip
CMake-8555c33d9237ae359f8d979af279af3809607a70.tar.gz
CMake-8555c33d9237ae359f8d979af279af3809607a70.tar.bz2
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
-rw-r--r--Help/command/cmake_pkg_config.rst261
-rw-r--r--Help/manual/cmake-commands.7.rst1
-rw-r--r--Source/CMakeLists.txt6
-rw-r--r--Source/cmCMakePkgConfigCommand.cxx722
-rw-r--r--Source/cmCMakePkgConfigCommand.h13
-rw-r--r--Source/cmCommands.cxx2
-rw-r--r--Source/cmPkgConfigParser.cxx151
-rw-r--r--Source/cmPkgConfigParser.h93
-rw-r--r--Source/cmPkgConfigResolver.cxx870
-rw-r--r--Source/cmPkgConfigResolver.h172
-rw-r--r--Tests/RunCMake/CMakeLists.txt1
-rw-r--r--Tests/RunCMake/cmake_pkg_config/CMakeLists.txt5
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/a.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/one.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/onedot.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/pseudo-empty.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/tilde.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/zeroone.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/all-extract-fields.pc15
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/bar.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/baz.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-bothcase-f.pc6
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-lowercase-f.pc5
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-uppercase-f.pc5
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/empty-key.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/foo.pc3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/invalid.pc4
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/no-description.pc2
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/no-name.pc2
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/no-version.pc2
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/qux-uninstalled.pc5
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/qux.pc5
-rw-r--r--Tests/RunCMake/cmake_pkg_config/PackageRoot/relocate.pc6
-rw-r--r--Tests/RunCMake/cmake_pkg_config/RunCMakeTest.cmake18
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestEnv-stderr.txt15
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestEnv.cmake75
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestExtract-stderr.txt21
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestExtract.cmake29
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestMangle-stderr.txt8
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestMangle.cmake22
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestQuiet.cmake29
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestRequired-result.txt1
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestRequired-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestRequired.cmake9
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestReroot-stderr.txt4
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestReroot.cmake10
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestStrictness-BEST_EFFORT-stderr.txt3
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestStrictness-PERMISSIVE-stderr.txt31
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestStrictness-STRICT-stderr.txt38
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestStrictness.cmake51
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestUninstalled-stderr.txt2
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestUninstalled.cmake10
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestVersion-stderr.txt103
-rw-r--r--Tests/RunCMake/cmake_pkg_config/TestVersion.cmake65
54 files changed, 2932 insertions, 0 deletions
diff --git a/Help/command/cmake_pkg_config.rst b/Help/command/cmake_pkg_config.rst
new file mode 100644
index 0000000..8136687
--- /dev/null
+++ b/Help/command/cmake_pkg_config.rst
@@ -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.
diff --git a/Help/manual/cmake-commands.7.rst b/Help/manual/cmake-commands.7.rst
index bd678b7..00f46aa 100644
--- a/Help/manual/cmake-commands.7.rst
+++ b/Help/manual/cmake-commands.7.rst
@@ -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
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index dab2dd3..6841f1d 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -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
diff --git a/Source/cmCMakePkgConfigCommand.cxx b/Source/cmCMakePkgConfigCommand.cxx
new file mode 100644
index 0000000..468d2ab
--- /dev/null
+++ b/Source/cmCMakePkgConfigCommand.cxx
@@ -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);
+}
diff --git a/Source/cmCMakePkgConfigCommand.h b/Source/cmCMakePkgConfigCommand.h
new file mode 100644
index 0000000..5898818
--- /dev/null
+++ b/Source/cmCMakePkgConfigCommand.h
@@ -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);
diff --git a/Source/cmCommands.cxx b/Source/cmCommands.cxx
index 91f7691..089bafc 100644
--- a/Source/cmCommands.cxx
+++ b/Source/cmCommands.cxx
@@ -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);
diff --git a/Source/cmPkgConfigParser.cxx b/Source/cmPkgConfigParser.cxx
new file mode 100644
index 0000000..992d1ea
--- /dev/null
+++ b/Source/cmPkgConfigParser.cxx
@@ -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();
+}
diff --git a/Source/cmPkgConfigParser.h b/Source/cmPkgConfigParser.h
new file mode 100644
index 0000000..e671c27
--- /dev/null
+++ b/Source/cmPkgConfigParser.h
@@ -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_;
+};
diff --git a/Source/cmPkgConfigResolver.cxx b/Source/cmPkgConfigResolver.cxx
new file mode 100644
index 0000000..251dfb7
--- /dev/null
+++ b/Source/cmPkgConfigResolver.cxx
@@ -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;
+}
diff --git a/Source/cmPkgConfigResolver.h b/Source/cmPkgConfigResolver.h
new file mode 100644
index 0000000..a230ae7
--- /dev/null
+++ b/Source/cmPkgConfigResolver.h
@@ -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);
+};
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 6f87b4e..2f4554f 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -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)
diff --git a/Tests/RunCMake/cmake_pkg_config/CMakeLists.txt b/Tests/RunCMake/cmake_pkg_config/CMakeLists.txt
new file mode 100644
index 0000000..b3f15ed
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/CMakeLists.txt
@@ -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)
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/a.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/a.pc
new file mode 100644
index 0000000..7492f30
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/a.pc
@@ -0,0 +1,3 @@
+Name:
+Version: aa
+Description:
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/one.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/one.pc
new file mode 100644
index 0000000..ec939ff
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/one.pc
@@ -0,0 +1,3 @@
+Name:
+Version: 11
+Description:
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/onedot.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/onedot.pc
new file mode 100644
index 0000000..e7d3cfd
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/onedot.pc
@@ -0,0 +1,3 @@
+Name:
+Version: 1.1.1
+Description:
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/pseudo-empty.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/pseudo-empty.pc
new file mode 100644
index 0000000..66d4163
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/pseudo-empty.pc
@@ -0,0 +1,3 @@
+Name:
+Version: ~0
+Description:
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/tilde.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/tilde.pc
new file mode 100644
index 0000000..8d8b38d
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/tilde.pc
@@ -0,0 +1,3 @@
+Name:
+Version: ~~1
+Description:
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/zeroone.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/zeroone.pc
new file mode 100644
index 0000000..265e8aa
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/VersionPackages/zeroone.pc
@@ -0,0 +1,3 @@
+Name:
+Version: 01
+Description:
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/all-extract-fields.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/all-extract-fields.pc
new file mode 100644
index 0000000..bd361fc
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/all-extract-fields.pc
@@ -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
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/bar.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/bar.pc
new file mode 100644
index 0000000..bf2942c
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/bar.pc
@@ -0,0 +1,3 @@
+Name: Bar
+Description: Bar Description
+Version: 1.0.0
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/baz.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/baz.pc
new file mode 100644
index 0000000..f1152d4
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/baz.pc
@@ -0,0 +1,3 @@
+Name: Baz
+Description: Baz Description
+Version: 1.0.0
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-bothcase-f.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-bothcase-f.pc
new file mode 100644
index 0000000..d242c11
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-bothcase-f.pc
@@ -0,0 +1,6 @@
+Name: Cflags Bothcase
+Description: The f is lowercase and uppercase
+Version: 1.0.0
+
+Cflags: lowercase
+CFlags: uppercase
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-lowercase-f.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-lowercase-f.pc
new file mode 100644
index 0000000..1640d8b
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-lowercase-f.pc
@@ -0,0 +1,5 @@
+Name: Cflags Lowercase
+Description: The f is lowercase
+Version: 1.0.0
+
+Cflags: lowercase
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-uppercase-f.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-uppercase-f.pc
new file mode 100644
index 0000000..0f7c308
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/cflags-uppercase-f.pc
@@ -0,0 +1,5 @@
+Name: CFlags Uppercase
+Description: The f is uppercase
+Version: 1.0.0
+
+CFlags: uppercase
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/empty-key.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/empty-key.pc
new file mode 100644
index 0000000..93ec694
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/empty-key.pc
@@ -0,0 +1,3 @@
+Name:
+Description:
+Version:
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/foo.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/foo.pc
new file mode 100644
index 0000000..0a98509
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/foo.pc
@@ -0,0 +1,3 @@
+Name: Foo
+Description: Foo Description
+Version: 1.0.0
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/invalid.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/invalid.pc
new file mode 100644
index 0000000..be1f433
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/invalid.pc
@@ -0,0 +1,4 @@
+Name: Invalid
+Description: Will cause a parse error
+Version: 1.0.0
+BrokenKey
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-description.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-description.pc
new file mode 100644
index 0000000..69007b5
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-description.pc
@@ -0,0 +1,2 @@
+Name: name
+Version: version
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-name.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-name.pc
new file mode 100644
index 0000000..5b878c0
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-name.pc
@@ -0,0 +1,2 @@
+Description: description
+Version: version
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-version.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-version.pc
new file mode 100644
index 0000000..850e26e
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/no-version.pc
@@ -0,0 +1,2 @@
+Name: name
+Description: description
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/qux-uninstalled.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/qux-uninstalled.pc
new file mode 100644
index 0000000..8f9462f
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/qux-uninstalled.pc
@@ -0,0 +1,5 @@
+Name: Qux
+Description: Qux Description
+Version: 1.0.0
+
+Cflags: QuxUninstalled
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/qux.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/qux.pc
new file mode 100644
index 0000000..6cf66b1
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/qux.pc
@@ -0,0 +1,5 @@
+Name: Qux
+Description: Qux Description
+Version: 1.0.0
+
+Cflags: QuxInstalled
diff --git a/Tests/RunCMake/cmake_pkg_config/PackageRoot/relocate.pc b/Tests/RunCMake/cmake_pkg_config/PackageRoot/relocate.pc
new file mode 100644
index 0000000..5abce76
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/PackageRoot/relocate.pc
@@ -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
diff --git a/Tests/RunCMake/cmake_pkg_config/RunCMakeTest.cmake b/Tests/RunCMake/cmake_pkg_config/RunCMakeTest.cmake
new file mode 100644
index 0000000..4f9200b
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/RunCMakeTest.cmake
@@ -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)
diff --git a/Tests/RunCMake/cmake_pkg_config/TestEnv-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestEnv-stderr.txt
new file mode 100644
index 0000000..67713c9
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestEnv-stderr.txt
@@ -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
diff --git a/Tests/RunCMake/cmake_pkg_config/TestEnv.cmake b/Tests/RunCMake/cmake_pkg_config/TestEnv.cmake
new file mode 100644
index 0000000..dcbb958
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestEnv.cmake
@@ -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}")
diff --git a/Tests/RunCMake/cmake_pkg_config/TestExtract-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestExtract-stderr.txt
new file mode 100644
index 0000000..42e4534
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestExtract-stderr.txt
@@ -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
diff --git a/Tests/RunCMake/cmake_pkg_config/TestExtract.cmake b/Tests/RunCMake/cmake_pkg_config/TestExtract.cmake
new file mode 100644
index 0000000..eb05966
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestExtract.cmake
@@ -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}")
diff --git a/Tests/RunCMake/cmake_pkg_config/TestMangle-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestMangle-stderr.txt
new file mode 100644
index 0000000..75557fc
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestMangle-stderr.txt
@@ -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
diff --git a/Tests/RunCMake/cmake_pkg_config/TestMangle.cmake b/Tests/RunCMake/cmake_pkg_config/TestMangle.cmake
new file mode 100644
index 0000000..b880d31
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestMangle.cmake
@@ -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}")
diff --git a/Tests/RunCMake/cmake_pkg_config/TestQuiet.cmake b/Tests/RunCMake/cmake_pkg_config/TestQuiet.cmake
new file mode 100644
index 0000000..ac72ab2
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestQuiet.cmake
@@ -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
+)
diff --git a/Tests/RunCMake/cmake_pkg_config/TestRequired-result.txt b/Tests/RunCMake/cmake_pkg_config/TestRequired-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestRequired-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/cmake_pkg_config/TestRequired-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestRequired-stderr.txt
new file mode 100644
index 0000000..d7f5158
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestRequired-stderr.txt
@@ -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\)
diff --git a/Tests/RunCMake/cmake_pkg_config/TestRequired.cmake b/Tests/RunCMake/cmake_pkg_config/TestRequired.cmake
new file mode 100644
index 0000000..fcc72ce
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestRequired.cmake
@@ -0,0 +1,9 @@
+cmake_pkg_config(
+ EXTRACT foo
+ REQUIRED
+)
+
+cmake_pkg_config(
+ EXTRACT does-not-exist
+ REQUIRED
+)
diff --git a/Tests/RunCMake/cmake_pkg_config/TestReroot-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestReroot-stderr.txt
new file mode 100644
index 0000000..ab524d4
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestReroot-stderr.txt
@@ -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
diff --git a/Tests/RunCMake/cmake_pkg_config/TestReroot.cmake b/Tests/RunCMake/cmake_pkg_config/TestReroot.cmake
new file mode 100644
index 0000000..0f55558
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestReroot.cmake
@@ -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}")
diff --git a/Tests/RunCMake/cmake_pkg_config/TestStrictness-BEST_EFFORT-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestStrictness-BEST_EFFORT-stderr.txt
new file mode 100644
index 0000000..e18e88e
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestStrictness-BEST_EFFORT-stderr.txt
@@ -0,0 +1,3 @@
+Cflags: lowercase
+CFlags: uppercase
+Cflags: lowercase uppercase
diff --git a/Tests/RunCMake/cmake_pkg_config/TestStrictness-PERMISSIVE-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestStrictness-PERMISSIVE-stderr.txt
new file mode 100644
index 0000000..2f4a69c
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestStrictness-PERMISSIVE-stderr.txt
@@ -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
diff --git a/Tests/RunCMake/cmake_pkg_config/TestStrictness-STRICT-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestStrictness-STRICT-stderr.txt
new file mode 100644
index 0000000..7329e8d
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestStrictness-STRICT-stderr.txt
@@ -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:
diff --git a/Tests/RunCMake/cmake_pkg_config/TestStrictness.cmake b/Tests/RunCMake/cmake_pkg_config/TestStrictness.cmake
new file mode 100644
index 0000000..d34f85e
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestStrictness.cmake
@@ -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}")
diff --git a/Tests/RunCMake/cmake_pkg_config/TestUninstalled-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestUninstalled-stderr.txt
new file mode 100644
index 0000000..25afa68
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestUninstalled-stderr.txt
@@ -0,0 +1,2 @@
+QuxUninstalled
+QuxInstalled
diff --git a/Tests/RunCMake/cmake_pkg_config/TestUninstalled.cmake b/Tests/RunCMake/cmake_pkg_config/TestUninstalled.cmake
new file mode 100644
index 0000000..fafed11
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestUninstalled.cmake
@@ -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})
diff --git a/Tests/RunCMake/cmake_pkg_config/TestVersion-stderr.txt b/Tests/RunCMake/cmake_pkg_config/TestVersion-stderr.txt
new file mode 100644
index 0000000..4b710d8
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestVersion-stderr.txt
@@ -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\)
diff --git a/Tests/RunCMake/cmake_pkg_config/TestVersion.cmake b/Tests/RunCMake/cmake_pkg_config/TestVersion.cmake
new file mode 100644
index 0000000..18fa587
--- /dev/null
+++ b/Tests/RunCMake/cmake_pkg_config/TestVersion.cmake
@@ -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