diff options
author | Vito Gamberini <vito.gamberini@kitware.com> | 2024-07-22 14:34:46 (GMT) |
---|---|---|
committer | Vito Gamberini <vito.gamberini@kitware.com> | 2024-07-29 17:14:46 (GMT) |
commit | 8555c33d9237ae359f8d979af279af3809607a70 (patch) | |
tree | a2f0f778ada30d4eca4216f0c97b3782a88c4177 | |
parent | 35734c8de3013c9e4bd2b2693640d2bacd39e49c (diff) | |
download | CMake-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
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 |