1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-14 02:08:27 +08:00

install(PACKAGE_INFO): Add version and location to package dependencies

Refactor `cmFindPackageStack` to track additional metadata
about <package> found. This includes two new fields,
`Version` and `Location` which correspond to package version and path.
The remaining package information will be implemented in a later commit
This commit is contained in:
Taylor Sasser
2025-07-31 12:05:32 -04:00
parent 43a86ba605
commit ae373e93fb
23 changed files with 277 additions and 24 deletions

View File

@@ -6,9 +6,10 @@ EXPORT_FIND_PACKAGE_NAME
Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_DEPENDENCIES``.
Control the package name associated with a dependency target when exporting a
:command:`find_dependency` call in :command:`install(EXPORT)` or
:command:`find_dependency` call in :command:`install(PACKAGE_INFO)`,
:command:`export(PACKAGE_INFO)`, :command:`install(EXPORT)` or
:command:`export(EXPORT)`. This can be used to assign a package name to a
package that is built by CMake and exported, or to override the package in the
:command:`find_package` call that created the target.
package that is built by CMake and exported, or a package that was provided by
:module:`FetchContent`.
This property is initialized by :variable:`CMAKE_EXPORT_FIND_PACKAGE_NAME`.

View File

@@ -5,9 +5,11 @@
#include <cstddef>
#include <memory>
#include <set>
#include <type_traits>
#include <utility>
#include <vector>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/algorithm>
#include <cmext/string_view>
@@ -27,7 +29,6 @@
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmValue.h"
static std::string const kCPS_VERSION_STR = "0.13.0";
@@ -146,15 +147,25 @@ void cmExportPackageInfoGenerator::GeneratePackageRequires(
auto data = Json::Value{ Json::objectValue };
// Add required components.
if (!requirement.second.empty()) {
if (!requirement.second.Components.empty()) {
auto components = Json::Value{ Json::arrayValue };
for (std::string const& component : requirement.second) {
for (std::string const& component : requirement.second.Components) {
components.append(component);
}
data["components"] = components;
}
// TODO: version, hint
// Add additional dependency information.
if (requirement.second.Directory) {
auto hints = Json::Value{ Json::arrayValue };
hints.append(*requirement.second.Directory);
data["hints"] = hints;
}
if (requirement.second.Version) {
data["version"] = *requirement.second.Version;
}
requirements[requirement.first] = data;
}
}
@@ -283,16 +294,24 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget(
if (linkedTarget->IsImported()) {
// Target is imported from a found package.
auto pkgName = [linkedTarget]() -> std::string {
auto const& pkgStack = linkedTarget->Target->GetFindPackageStack();
using Package = cm::optional<std::pair<std::string, cmPackageInformation>>;
auto pkgInfo = [](cmTarget* t) -> Package {
cmFindPackageStack pkgStack = t->GetFindPackageStack();
if (!pkgStack.Empty()) {
return pkgStack.Top().Name;
return std::make_pair(pkgStack.Top().Name, pkgStack.Top().PackageInfo);
}
return linkedTarget->Target->GetProperty("EXPORT_FIND_PACKAGE_NAME");
}();
cmPackageInformation package;
std::string const pkgName =
t->GetSafeProperty("EXPORT_FIND_PACKAGE_NAME");
if (pkgName.empty()) {
return cm::nullopt;
}
if (pkgName.empty()) {
return std::make_pair(pkgName, package);
}(linkedTarget->Target);
if (!pkgInfo) {
target->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Target \"", target->GetName(),
@@ -301,6 +320,8 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget(
return false;
}
std::string const& pkgName = pkgInfo->first;
auto const& prefix = cmStrCat(pkgName, "::");
if (!cmHasPrefix(linkedName, prefix)) {
target->Makefile->IssueMessage(
@@ -314,8 +335,9 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget(
std::string component = linkedName.substr(prefix.length());
this->LinkTargets.emplace(linkedName, cmStrCat(pkgName, ':', component));
// TODO: Record package version, hint.
this->Requirements[pkgName].emplace(std::move(component));
cmPackageInformation& req =
this->Requirements.insert(std::move(*pkgInfo)).first->second;
req.Components.emplace(std::move(component));
return true;
}
@@ -339,7 +361,7 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget(
this->LinkTargets.emplace(linkedName, cmStrCat(':', component));
} else {
this->LinkTargets.emplace(linkedName, cmStrCat(pkgName, ':', component));
this->Requirements[pkgName].emplace(std::move(component));
this->Requirements[pkgName].Components.emplace(std::move(component));
}
return true;
}

View File

@@ -6,13 +6,13 @@
#include <iosfwd>
#include <map>
#include <set>
#include <string>
#include <vector>
#include <cm/string_view>
#include "cmExportFileGenerator.h"
#include "cmFindPackageStack.h"
#include "cmStateTypes.h"
namespace Json {
@@ -112,9 +112,10 @@ private:
std::string const PackageWebsite;
std::string const PackageLicense;
std::string const DefaultLicense;
std::vector<std::string> DefaultTargets;
std::vector<std::string> DefaultConfigurations;
std::map<std::string, std::string> LinkTargets;
std::map<std::string, std::set<std::string>> Requirements;
std::map<std::string, cmPackageInformation> Requirements;
};

View File

@@ -1224,6 +1224,8 @@ bool cmFindPackageCommand::FindPackage(
SetRestoreFindDefinitions setRestoreFindDefinitions(*this);
cmFindPackageStackRAII findPackageStackRAII(this->Makefile, this->Name);
findPackageStackRAII.BindTop(this->CurrentPackageInfo);
// See if we have been told to delegate to FetchContent or some other
// redirected config package first. We have to check all names that
// find_package() may look for, but only need to invoke the override for the
@@ -1270,6 +1272,8 @@ bool cmFindPackageCommand::FindPackage(
this->Names.clear();
this->Names.emplace_back(overrideName); // Force finding this one
this->Variable = cmStrCat(this->Name, "_DIR");
this->CurrentPackageInfo->Directory = redirectsDir;
this->CurrentPackageInfo->Version = this->VersionFound;
this->SetConfigDirCacheVariable(redirectsDir);
break;
}
@@ -1342,7 +1346,6 @@ bool cmFindPackageCommand::FindPackage(
}
this->AppendSuccessInformation();
return loadedPackage;
}
@@ -1971,6 +1974,8 @@ bool cmFindPackageCommand::FindConfig()
std::string init;
if (found) {
init = cmSystemTools::GetFilenamePath(this->FileFound);
this->CurrentPackageInfo->Directory = init;
this->CurrentPackageInfo->Version = this->VersionFound;
} else {
init = this->Variable + "-NOTFOUND";
}

View File

@@ -37,6 +37,7 @@ class cmExecutionStatus;
class cmMakefile;
class cmPackageState;
class cmSearchPath;
class cmPackageInformation;
/** \class cmFindPackageCommand
* \brief Load settings from an external project.
@@ -286,6 +287,8 @@ private:
std::set<std::string> OptionalComponents;
std::set<std::string> RequiredTargets;
std::string DebugBuffer;
cmPackageInformation* CurrentPackageInfo;
enum class SearchResult
{
InsufficientVersion,

View File

@@ -5,19 +5,41 @@
#include "cmConfigure.h" // IWYU pragma: keep
#include <memory>
#include <set>
#include <string>
#include <cm/optional>
#include "cmStack.h"
class cmMakefile;
/**
* This data represents the actual contents of find_package
* <PACKAGE>-Config.cmake or <PACKAGE>.cps file, and not what is passed
* to the find_package command. They can be the same, but it is not guaranteed.
*/
class cmPackageInformation
{
public:
cm::optional<std::string> Directory;
cm::optional<std::string> Version;
cm::optional<std::string> Description;
cm::optional<std::string> License;
cm::optional<std::string> Website;
cm::optional<std::string> PackageUrl;
std::set<std::string> Components;
};
/**
* Represents one call to find_package.
*/
class cmFindPackageCall
{
public:
std::string Name;
std::string const Name;
cmPackageInformation PackageInfo;
unsigned int Index;
};
@@ -28,7 +50,7 @@ public:
class cmFindPackageStackRAII
{
cmMakefile* Makefile;
cmFindPackageCall** Value = nullptr;
cmPackageInformation** Value = nullptr;
public:
cmFindPackageStackRAII(cmMakefile* mf, std::string const& pkg);
@@ -40,7 +62,7 @@ public:
/** Get a mutable pointer to the top of the stack.
The pointer is invalidated if BindTop is called again or when the
cmFindPackageStackRAII goes out of scope. */
void BindTop(cmFindPackageCall*& value);
void BindTop(cmPackageInformation*& value);
};
/**

View File

@@ -4238,18 +4238,19 @@ cmFindPackageStackRAII::cmFindPackageStackRAII(cmMakefile* mf,
this->Makefile->FindPackageStack =
this->Makefile->FindPackageStack.Push(cmFindPackageCall{
name,
cmPackageInformation(),
this->Makefile->FindPackageStackNextIndex,
});
this->Makefile->FindPackageStackNextIndex++;
}
void cmFindPackageStackRAII::BindTop(cmFindPackageCall*& value)
void cmFindPackageStackRAII::BindTop(cmPackageInformation*& value)
{
if (this->Value) {
*this->Value = nullptr;
}
this->Value = &value;
value = &this->Makefile->FindPackageStack.cmStack::Top();
value = &this->Makefile->FindPackageStack.cmStack::Top().PackageInfo;
}
cmFindPackageStackRAII::~cmFindPackageStackRAII()

View File

@@ -0,0 +1,15 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/DependencyVersionCMake-build")
file(READ "${out_dir}/foo.cps" content)
expect_value("${content}" "foo" "name")
expect_array("${content}" 1 "requires" "bar" "components")
expect_value("${content}" "bar" "requires" "bar" "components" 0)
expect_value("${content}" "1.3.5" "requires" "bar" "version")
expect_array("${content}" 1 "requires" "bar" "hints")
expect_value("${content}" "${CMAKE_CURRENT_LIST_DIR}/config" "requires" "bar" "hints" 0)
string(JSON component GET "${content}" "components" "foo")
expect_array("${component}" 1 "requires")
expect_value("${component}" "bar:bar" "requires" 0)

View File

@@ -0,0 +1,11 @@
find_package(
bar 1.3.4 REQUIRED CONFIG
NO_DEFAULT_PATH
PATHS ${CMAKE_CURRENT_LIST_DIR}/config
)
add_library(foo INTERFACE)
target_link_libraries(foo INTERFACE bar::bar)
install(TARGETS foo EXPORT foo DESTINATION .)
export(EXPORT foo PACKAGE_INFO foo)

View File

@@ -0,0 +1,15 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/DependencyVersionCps-build/")
file(READ "${out_dir}/foo.cps" content)
expect_value("${content}" "foo" "name")
expect_array("${content}" 1 "requires" "baz" "components")
expect_value("${content}" "baz" "requires" "baz" "components" 0)
expect_value("${content}" "1.3.5" "requires" "baz" "version")
expect_array("${content}" 1 "requires" "baz" "hints")
expect_value("${content}" "${CMAKE_CURRENT_LIST_DIR}/cps" "requires" "baz" "hints" 0)
string(JSON component GET "${content}" "components" "foo")
expect_array("${component}" 1 "requires")
expect_value("${component}" "baz:baz" "requires" 0)

View File

@@ -0,0 +1,11 @@
find_package(
baz 1.3.4 REQUIRED
NO_DEFAULT_PATH
PATHS ${CMAKE_CURRENT_LIST_DIR}
)
add_library(foo INTERFACE)
target_link_libraries(foo INTERFACE baz::baz)
install(TARGETS foo EXPORT foo DESTINATION .)
export(EXPORT foo PACKAGE_INFO foo)

View File

@@ -8,6 +8,7 @@ run_cmake(ExperimentalWarning)
set(RunCMake_TEST_OPTIONS
-Wno-dev
"-DCMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO:STRING=b80be207-778e-46ba-8080-b23bba22639e"
"-DCMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES:STRING=e82e467b-f997-4464-8ace-b00808fff261"
)
# Test incorrect usage
@@ -43,3 +44,5 @@ run_cmake(LinkOnly)
run_cmake(Config)
run_cmake(EmptyConfig)
run_cmake(FileSetHeaders)
run_cmake(DependencyVersionCMake)
run_cmake(DependencyVersionCps)

View File

@@ -0,0 +1,29 @@
set(PACKAGE_VERSION "1.3.5")
if (PACKAGE_FIND_VERSION_RANGE)
# Check for a version range
if (PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_RANGE_MIN)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else ()
if (PACKAGE_FIND_VERSION_RANGE_MAX)
if (PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_RANGE_MAX)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else ()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
endif ()
else ()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
endif ()
endif ()
elseif (PACKAGE_FIND_VERSION)
# Check for a specific version or minimum version
if (PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else ()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if (PACKAGE_VERSION VERSION_EQUAL PACKAGE_FIND_VERSION)
set(PACKAGE_VERSION_EXACT TRUE)
endif ()
endif ()
endif ()

View File

@@ -0,0 +1 @@
add_library(bar::bar INTERFACE IMPORTED)

View File

@@ -0,0 +1,14 @@
{
"components" :
{
"baz" :
{
"type" : "interface"
}
},
"cps_path" : "@prefix@/cps",
"cps_version" : "0.13.0",
"compat_version": "1.0.0",
"name" : "baz",
"version": "1.3.5"
}

View File

@@ -0,0 +1,15 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/DependencyVersionCMake-build/CMakeFiles/Export/5058f1af8388633f609cadb75a75dc9d")
file(READ "${out_dir}/foo.cps" content)
expect_value("${content}" "foo" "name")
expect_array("${content}" 1 "requires" "bar" "components")
expect_value("${content}" "bar" "requires" "bar" "components" 0)
expect_value("${content}" "1.3.5" "requires" "bar" "version")
expect_array("${content}" 1 "requires" "bar" "hints")
expect_value("${content}" "${CMAKE_CURRENT_LIST_DIR}/config" "requires" "bar" "hints" 0)
string(JSON component GET "${content}" "components" "foo")
expect_array("${component}" 1 "requires")
expect_value("${component}" "bar:bar" "requires" 0)

View File

@@ -0,0 +1,11 @@
find_package(
bar 1.3.4 REQUIRED CONFIG
NO_DEFAULT_PATH
PATHS ${CMAKE_CURRENT_LIST_DIR}/config
)
add_library(foo INTERFACE)
target_link_libraries(foo INTERFACE bar::bar)
install(TARGETS foo EXPORT foo)
install(PACKAGE_INFO foo EXPORT foo DESTINATION .)

View File

@@ -0,0 +1,15 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/DependencyVersionCps-build/CMakeFiles/Export/5058f1af8388633f609cadb75a75dc9d")
file(READ "${out_dir}/foo.cps" content)
expect_value("${content}" "foo" "name")
expect_array("${content}" 1 "requires" "baz" "components")
expect_value("${content}" "baz" "requires" "baz" "components" 0)
expect_value("${content}" "1.3.5" "requires" "baz" "version")
expect_array("${content}" 1 "requires" "baz" "hints")
expect_value("${content}" "${CMAKE_CURRENT_LIST_DIR}/cps" "requires" "baz" "hints" 0)
string(JSON component GET "${content}" "components" "foo")
expect_array("${component}" 1 "requires")
expect_value("${component}" "baz:baz" "requires" 0)

View File

@@ -0,0 +1,11 @@
find_package(
baz 1.3.4 REQUIRED
NO_DEFAULT_PATH
PATHS ${CMAKE_CURRENT_LIST_DIR}
)
add_library(foo INTERFACE)
target_link_libraries(foo INTERFACE baz::baz)
install(TARGETS foo EXPORT foo)
install(PACKAGE_INFO foo EXPORT foo DESTINATION .)

View File

@@ -8,6 +8,7 @@ run_cmake(ExperimentalWarning)
set(RunCMake_TEST_OPTIONS
-Wno-dev
"-DCMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO:STRING=b80be207-778e-46ba-8080-b23bba22639e"
"-DCMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES:STRING=e82e467b-f997-4464-8ace-b00808fff261"
)
function(run_cmake_install test)
@@ -51,4 +52,6 @@ run_cmake(DependsMultipleNotInstalled)
run_cmake(Config)
run_cmake(EmptyConfig)
run_cmake(FileSetHeaders)
run_cmake(DependencyVersionCMake)
run_cmake(DependencyVersionCps)
run_cmake_install(Destination)

View File

@@ -0,0 +1,29 @@
set(PACKAGE_VERSION "1.3.5")
if (PACKAGE_FIND_VERSION_RANGE)
# Check for a version range
if (PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_RANGE_MIN)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else ()
if (PACKAGE_FIND_VERSION_RANGE_MAX)
if (PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_RANGE_MAX)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else ()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
endif ()
else ()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
endif ()
endif ()
elseif (PACKAGE_FIND_VERSION)
# Check for a specific version or minimum version
if (PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else ()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if (PACKAGE_VERSION VERSION_EQUAL PACKAGE_FIND_VERSION)
set(PACKAGE_VERSION_EXACT TRUE)
endif ()
endif ()
endif ()

View File

@@ -0,0 +1 @@
add_library(bar::bar INTERFACE IMPORTED)

View File

@@ -0,0 +1,14 @@
{
"components" :
{
"baz" :
{
"type" : "interface"
}
},
"cps_path" : "@prefix@/cps",
"cps_version" : "0.13.0",
"compat_version": "1.0.0",
"name" : "baz",
"version": "1.3.5"
}