mirror of
https://github.com/Kitware/CMake.git
synced 2025-10-18 08:51:52 +08:00
find_package: Actually find .cps files
Add a helper class to read CPS files. Use this to teach find_package how to consider and accept .cps files in its search. (Note that no version testing is performed at this time.) Add a simple test that we can find a package from a .cps file and correctly extract the version information. Note that this doesn't actually import anything from CPS yet.
This commit is contained in:

committed by
Brad King

parent
b89e43b2bc
commit
91c31ada23
@@ -412,6 +412,8 @@ add_library(
|
|||||||
cmNewLineStyle.cxx
|
cmNewLineStyle.cxx
|
||||||
cmOrderDirectories.cxx
|
cmOrderDirectories.cxx
|
||||||
cmOrderDirectories.h
|
cmOrderDirectories.h
|
||||||
|
cmPackageInfoReader.cxx
|
||||||
|
cmPackageInfoReader.h
|
||||||
cmPathResolver.cxx
|
cmPathResolver.cxx
|
||||||
cmPathResolver.h
|
cmPathResolver.h
|
||||||
cmPlistParser.cxx
|
cmPlistParser.cxx
|
||||||
|
@@ -1503,7 +1503,12 @@ bool cmFindPackageCommand::HandlePackageMode(
|
|||||||
this->StoreVersionFound();
|
this->StoreVersionFound();
|
||||||
|
|
||||||
// Parse the configuration file.
|
// Parse the configuration file.
|
||||||
if (this->ReadListFile(this->FileFound, DoPolicyScope)) {
|
if (this->CpsReader) {
|
||||||
|
// FIXME TODO
|
||||||
|
|
||||||
|
// The package has been found.
|
||||||
|
found = true;
|
||||||
|
} else if (this->ReadListFile(this->FileFound, DoPolicyScope)) {
|
||||||
// The package has been found.
|
// The package has been found.
|
||||||
found = true;
|
found = true;
|
||||||
|
|
||||||
@@ -2487,8 +2492,43 @@ bool cmFindPackageCommand::CheckVersion(std::string const& config_file)
|
|||||||
bool haveResult = false;
|
bool haveResult = false;
|
||||||
std::string version = "unknown";
|
std::string version = "unknown";
|
||||||
|
|
||||||
// Get the filename without the .cmake extension.
|
// Get the file extension.
|
||||||
std::string::size_type pos = config_file.rfind('.');
|
std::string::size_type pos = config_file.rfind('.');
|
||||||
|
std::string ext = cmSystemTools::LowerCase(config_file.substr(pos));
|
||||||
|
|
||||||
|
if (ext == ".cps"_s) {
|
||||||
|
std::unique_ptr<cmPackageInfoReader> reader =
|
||||||
|
cmPackageInfoReader::Read(config_file);
|
||||||
|
if (reader && reader->GetName() == this->Name) {
|
||||||
|
cm::optional<std::string> cpsVersion = reader->GetVersion();
|
||||||
|
if (cpsVersion) {
|
||||||
|
// TODO: Implement version check for CPS
|
||||||
|
this->VersionFound = (version = std::move(*cpsVersion));
|
||||||
|
|
||||||
|
std::vector<unsigned> const& versionParts = reader->ParseVersion();
|
||||||
|
this->VersionFoundCount = static_cast<unsigned>(versionParts.size());
|
||||||
|
switch (this->VersionFoundCount) {
|
||||||
|
case 4:
|
||||||
|
this->VersionFoundTweak = versionParts[3];
|
||||||
|
CM_FALLTHROUGH;
|
||||||
|
case 3:
|
||||||
|
this->VersionFoundPatch = versionParts[2];
|
||||||
|
CM_FALLTHROUGH;
|
||||||
|
case 2:
|
||||||
|
this->VersionFoundMinor = versionParts[1];
|
||||||
|
CM_FALLTHROUGH;
|
||||||
|
case 1:
|
||||||
|
this->VersionFoundMajor = versionParts[0];
|
||||||
|
CM_FALLTHROUGH;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->CpsReader = std::move(reader);
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Get the filename without the .cmake extension.
|
||||||
std::string version_file_base = config_file.substr(0, pos);
|
std::string version_file_base = config_file.substr(0, pos);
|
||||||
|
|
||||||
// Look for foo-config-version.cmake
|
// Look for foo-config-version.cmake
|
||||||
@@ -2509,6 +2549,7 @@ bool cmFindPackageCommand::CheckVersion(std::string const& config_file)
|
|||||||
if (!haveResult && this->Version.empty()) {
|
if (!haveResult && this->Version.empty()) {
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConfigFileInfo configFileInfo;
|
ConfigFileInfo configFileInfo;
|
||||||
configFileInfo.filename = config_file;
|
configFileInfo.filename = config_file;
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
#include <cm3p/kwiml/int.h>
|
#include <cm3p/kwiml/int.h>
|
||||||
|
|
||||||
#include "cmFindCommon.h"
|
#include "cmFindCommon.h"
|
||||||
|
#include "cmPackageInfoReader.h"
|
||||||
#include "cmPolicies.h"
|
#include "cmPolicies.h"
|
||||||
|
|
||||||
// IWYU insists we should forward-declare instead of including <functional>,
|
// IWYU insists we should forward-declare instead of including <functional>,
|
||||||
@@ -283,6 +285,8 @@ private:
|
|||||||
};
|
};
|
||||||
std::vector<ConfigFileInfo> ConsideredConfigs;
|
std::vector<ConfigFileInfo> ConsideredConfigs;
|
||||||
|
|
||||||
|
std::unique_ptr<cmPackageInfoReader> CpsReader;
|
||||||
|
|
||||||
friend struct std::hash<ConfigFileInfo>;
|
friend struct std::hash<ConfigFileInfo>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
146
Source/cmPackageInfoReader.cxx
Normal file
146
Source/cmPackageInfoReader.cxx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||||
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||||
|
#include "cmPackageInfoReader.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <cm/string_view>
|
||||||
|
#include <cmext/string_view>
|
||||||
|
|
||||||
|
#include <cm3p/json/reader.h>
|
||||||
|
#include <cm3p/json/value.h>
|
||||||
|
#include <cm3p/json/version.h>
|
||||||
|
|
||||||
|
#include "cmsys/FStream.hxx"
|
||||||
|
|
||||||
|
#include "cmStringAlgorithms.h"
|
||||||
|
#include "cmSystemTools.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Json::Value ReadJson(std::string const& fileName)
|
||||||
|
{
|
||||||
|
// Open the specified file.
|
||||||
|
cmsys::ifstream file(fileName.c_str(), std::ios::in | std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
#if JSONCPP_VERSION_HEXA < 0x01070300
|
||||||
|
return Json::Value::null;
|
||||||
|
#else
|
||||||
|
return Json::Value::nullSingleton();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read file content and translate JSON.
|
||||||
|
Json::Value data;
|
||||||
|
Json::CharReaderBuilder builder;
|
||||||
|
builder["collectComments"] = false;
|
||||||
|
if (!Json::parseFromStream(builder, file, &data, nullptr)) {
|
||||||
|
#if JSONCPP_VERSION_HEXA < 0x01070300
|
||||||
|
return Json::Value::null;
|
||||||
|
#else
|
||||||
|
return Json::Value::nullSingleton();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckSchemaVersion(Json::Value const& data)
|
||||||
|
{
|
||||||
|
std::string const& version = data["cps_version"].asString();
|
||||||
|
|
||||||
|
// Check that a valid version is specified.
|
||||||
|
if (version.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we understand this version.
|
||||||
|
return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL,
|
||||||
|
version, "0.12") &&
|
||||||
|
cmSystemTools::VersionCompare(cmSystemTools::OP_LESS, version, "1");
|
||||||
|
|
||||||
|
// TODO Eventually this probably needs to return the version tuple, and
|
||||||
|
// should share code with cmPackageInfoReader::ParseVersion.
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<cmPackageInfoReader> cmPackageInfoReader::Read(
|
||||||
|
std::string const& path)
|
||||||
|
{
|
||||||
|
// Read file and perform some basic validation:
|
||||||
|
// - the input is valid JSON
|
||||||
|
// - the input is a JSON object
|
||||||
|
// - the input has a "cps_version" that we (in theory) know how to parse
|
||||||
|
Json::Value data = ReadJson(path);
|
||||||
|
if (!data.isObject() || !CheckSchemaVersion(data)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - the input has a "name" attribute that is a non-empty string
|
||||||
|
Json::Value const& name = data["name"];
|
||||||
|
if (!name.isString() || name.empty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - the input has a "components" attribute that is a JSON object
|
||||||
|
if (!data["components"].isObject()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seems sane enough to hand back to the caller.
|
||||||
|
std::unique_ptr<cmPackageInfoReader> reader{ new cmPackageInfoReader };
|
||||||
|
reader->Data = data;
|
||||||
|
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cmPackageInfoReader::GetName() const
|
||||||
|
{
|
||||||
|
return this->Data["name"].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
cm::optional<std::string> cmPackageInfoReader::GetVersion() const
|
||||||
|
{
|
||||||
|
Json::Value const& version = this->Data["version"];
|
||||||
|
if (version.isString()) {
|
||||||
|
return version.asString();
|
||||||
|
}
|
||||||
|
return cm::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned> cmPackageInfoReader::ParseVersion() const
|
||||||
|
{
|
||||||
|
// Check that we have a version.
|
||||||
|
cm::optional<std::string> const& version = this->GetVersion();
|
||||||
|
if (!version) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned> result;
|
||||||
|
cm::string_view remnant{ *version };
|
||||||
|
|
||||||
|
// Check if we know how to parse the version.
|
||||||
|
Json::Value const& schema = this->Data["version_schema"];
|
||||||
|
if (schema.isNull() || cmStrCaseEq(schema.asString(), "simple"_s)) {
|
||||||
|
// Keep going until we run out of parts.
|
||||||
|
while (!remnant.empty()) {
|
||||||
|
std::string::size_type n = remnant.find('.');
|
||||||
|
cm::string_view part = remnant.substr(0, n);
|
||||||
|
if (n == std::string::npos) {
|
||||||
|
remnant = {};
|
||||||
|
} else {
|
||||||
|
remnant = remnant.substr(n + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long const value = std::stoul(std::string{ part }, &n);
|
||||||
|
if (n == 0 || value > std::numeric_limits<unsigned>::max()) {
|
||||||
|
// The part was not a valid number or is too big.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
result.push_back(static_cast<unsigned>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
42
Source/cmPackageInfoReader.h
Normal file
42
Source/cmPackageInfoReader.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/* 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 <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <cm/optional>
|
||||||
|
|
||||||
|
#include <cm3p/json/value.h>
|
||||||
|
|
||||||
|
// class cmExecutionStatus;
|
||||||
|
|
||||||
|
/** \class cmPackageInfoReader
|
||||||
|
* \brief Read and parse CPS files.
|
||||||
|
*
|
||||||
|
* This class encapsulates the functionality to read package configuration
|
||||||
|
* files which use the Common Package Specification, and provides utilities to
|
||||||
|
* translate the declarations therein into imported targets.
|
||||||
|
*/
|
||||||
|
class cmPackageInfoReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::unique_ptr<cmPackageInfoReader> Read(std::string const& path);
|
||||||
|
|
||||||
|
std::string GetName() const;
|
||||||
|
cm::optional<std::string> GetVersion() const;
|
||||||
|
|
||||||
|
/// If the package uses the 'simple' version scheme, obtain the version as
|
||||||
|
/// a numeric tuple. Returns an empty vector for other schemes or if no
|
||||||
|
/// version is specified.
|
||||||
|
std::vector<unsigned> ParseVersion() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
cmPackageInfoReader() = default;
|
||||||
|
|
||||||
|
std::string Path;
|
||||||
|
Json::Value Data;
|
||||||
|
};
|
@@ -352,6 +352,7 @@ if(BUILD_TESTING)
|
|||||||
# add a bunch of standard build-and-test style tests
|
# add a bunch of standard build-and-test style tests
|
||||||
ADD_TEST_MACRO(CommandLineTest CommandLineTest)
|
ADD_TEST_MACRO(CommandLineTest CommandLineTest)
|
||||||
ADD_TEST_MACRO(FindPackageCMakeTest FindPackageCMakeTest)
|
ADD_TEST_MACRO(FindPackageCMakeTest FindPackageCMakeTest)
|
||||||
|
ADD_TEST_MACRO(FindPackageCpsTest FindPackageCpsTest)
|
||||||
ADD_TEST_MACRO(StringFileTest StringFileTest)
|
ADD_TEST_MACRO(StringFileTest StringFileTest)
|
||||||
ADD_TEST_MACRO(TryCompile TryCompile)
|
ADD_TEST_MACRO(TryCompile TryCompile)
|
||||||
ADD_TEST_MACRO(SystemInformation SystemInformation)
|
ADD_TEST_MACRO(SystemInformation SystemInformation)
|
||||||
|
38
Tests/FindPackageCpsTest/CMakeLists.txt
Normal file
38
Tests/FindPackageCpsTest/CMakeLists.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.31)
|
||||||
|
project(FindPackageCpsTest)
|
||||||
|
|
||||||
|
# Protect tests from running inside the default install prefix.
|
||||||
|
set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/NotDefaultPrefix")
|
||||||
|
|
||||||
|
# Disable built-in search paths.
|
||||||
|
set(CMAKE_FIND_USE_PACKAGE_ROOT_PATH OFF)
|
||||||
|
set(CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH OFF)
|
||||||
|
set(CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH OFF)
|
||||||
|
set(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH OFF)
|
||||||
|
set(CMAKE_FIND_USE_INSTALL_PREFIX OFF)
|
||||||
|
|
||||||
|
# Enable framework searching.
|
||||||
|
set(CMAKE_FIND_FRAMEWORK FIRST)
|
||||||
|
|
||||||
|
add_executable(FindPackageCpsTest FindPackageTest.cxx)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Test a basic package search.
|
||||||
|
set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
find_package(Sample CONFIG)
|
||||||
|
if(NOT Sample_FOUND)
|
||||||
|
message(SEND_ERROR "Sample not found !")
|
||||||
|
elseif(NOT Sample_VERSION STREQUAL "2.10.11")
|
||||||
|
message(SEND_ERROR "Sample wrong version ${Sample_VERSION} !")
|
||||||
|
elseif(NOT Sample_VERSION_MAJOR EQUAL 2)
|
||||||
|
message(SEND_ERROR "Sample wrong major version ${Sample_VERSION_MAJOR} !")
|
||||||
|
elseif(NOT Sample_VERSION_MINOR EQUAL 10)
|
||||||
|
message(SEND_ERROR "Sample wrong minor version ${Sample_VERSION_MINOR} !")
|
||||||
|
elseif(NOT Sample_VERSION_PATCH EQUAL 11)
|
||||||
|
message(SEND_ERROR "Sample wrong patch version ${Sample_VERSION_PATCH} !")
|
||||||
|
elseif(NOT Sample_VERSION_TWEAK EQUAL 0)
|
||||||
|
message(SEND_ERROR "Sample wrong tweak version ${Sample_VERSION_TWEAK} !")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_PREFIX_PATH)
|
4
Tests/FindPackageCpsTest/FindPackageTest.cxx
Normal file
4
Tests/FindPackageCpsTest/FindPackageTest.cxx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
int main()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
8
Tests/FindPackageCpsTest/cps/sample.cps
Normal file
8
Tests/FindPackageCpsTest/cps/sample.cps
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"cps_version": "0.13",
|
||||||
|
"name": "Sample",
|
||||||
|
"version": "2.10.11",
|
||||||
|
"compat_version": "2.0.0",
|
||||||
|
"cps_path": "@prefix@/cps",
|
||||||
|
"components": {}
|
||||||
|
}
|
@@ -468,6 +468,7 @@ CMAKE_CXX_SOURCES="\
|
|||||||
cmGccDepfileLexerHelper \
|
cmGccDepfileLexerHelper \
|
||||||
cmGccDepfileReader \
|
cmGccDepfileReader \
|
||||||
cmReturnCommand \
|
cmReturnCommand \
|
||||||
|
cmPackageInfoReader \
|
||||||
cmPlaceholderExpander \
|
cmPlaceholderExpander \
|
||||||
cmPlistParser \
|
cmPlistParser \
|
||||||
cmRulePlaceholderExpander \
|
cmRulePlaceholderExpander \
|
||||||
|
Reference in New Issue
Block a user