Browse Source

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
Taylor Sasser 4 months ago
parent
commit
ae373e93fb

+ 4 - 3
Help/prop_tgt/EXPORT_FIND_PACKAGE_NAME.rst

@@ -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`.

+ 35 - 13
Source/cmExportPackageInfoGenerator.cxx

@@ -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;
+      }
+
+      return std::make_pair(pkgName, package);
+    }(linkedTarget->Target);
 
-    if (pkgName.empty()) {
+    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;
   }

+ 3 - 2
Source/cmExportPackageInfoGenerator.h

@@ -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;
 };

+ 6 - 1
Source/cmFindPackageCommand.cxx

@@ -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";
   }

+ 3 - 0
Source/cmFindPackageCommand.h

@@ -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,

+ 25 - 3
Source/cmFindPackageStack.h

@@ -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);
 };
 
 /**

+ 3 - 2
Source/cmMakefile.cxx

@@ -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()

+ 15 - 0
Tests/RunCMake/ExportPackageInfo/DependencyVersionCMake-check.cmake

@@ -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)

+ 11 - 0
Tests/RunCMake/ExportPackageInfo/DependencyVersionCMake.cmake

@@ -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)

+ 15 - 0
Tests/RunCMake/ExportPackageInfo/DependencyVersionCps-check.cmake

@@ -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)

+ 11 - 0
Tests/RunCMake/ExportPackageInfo/DependencyVersionCps.cmake

@@ -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)

+ 3 - 0
Tests/RunCMake/ExportPackageInfo/RunCMakeTest.cmake

@@ -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)

+ 29 - 0
Tests/RunCMake/ExportPackageInfo/config/bar-config-version.cmake

@@ -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 ()

+ 1 - 0
Tests/RunCMake/ExportPackageInfo/config/bar-config.cmake

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

+ 14 - 0
Tests/RunCMake/ExportPackageInfo/cps/baz.cps

@@ -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"
+}

+ 15 - 0
Tests/RunCMake/InstallPackageInfo/DependencyVersionCMake-check.cmake

@@ -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)

+ 11 - 0
Tests/RunCMake/InstallPackageInfo/DependencyVersionCMake.cmake

@@ -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 .)

+ 15 - 0
Tests/RunCMake/InstallPackageInfo/DependencyVersionCps-check.cmake

@@ -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)

+ 11 - 0
Tests/RunCMake/InstallPackageInfo/DependencyVersionCps.cmake

@@ -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 .)

+ 3 - 0
Tests/RunCMake/InstallPackageInfo/RunCMakeTest.cmake

@@ -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)

+ 29 - 0
Tests/RunCMake/InstallPackageInfo/config/bar-config-version.cmake

@@ -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 ()

+ 1 - 0
Tests/RunCMake/InstallPackageInfo/config/bar-config.cmake

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

+ 14 - 0
Tests/RunCMake/InstallPackageInfo/cps/baz.cps

@@ -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"
+}