Browse Source

find_package: Find CPS components

Implement finding components of CPS packages. Specifically, reject any
candidate packages that don't provide all required components, and
ignore appendices that don't provide requested (required or optional)
components. This applies to both top-level searches and also searching
for package dependencies.
Matthew Woehlke 9 months ago
parent
commit
894f256a12
33 changed files with 485 additions and 42 deletions
  1. 3 3
      Help/command/find_package.rst
  2. 101 36
      Source/cmFindPackageCommand.cxx
  3. 11 3
      Source/cmFindPackageCommand.h
  4. 12 0
      Source/cmPackageInfoReader.cxx
  5. 1 0
      Source/cmPackageInfoReader.h
  6. 46 0
      Tests/FindPackageCpsTest/CMakeLists.txt
  7. 12 0
      Tests/FindPackageCpsTest/cps/ComponentTest-extra1.cps
  8. 12 0
      Tests/FindPackageCpsTest/cps/ComponentTest-extra2.cps
  9. 15 0
      Tests/FindPackageCpsTest/cps/ComponentTest-extra3.cps
  10. 9 0
      Tests/FindPackageCpsTest/cps/ComponentTest-extra4.cps
  11. 10 0
      Tests/FindPackageCpsTest/cps/ComponentTest.cps
  12. 12 0
      Tests/FindPackageCpsTest/cps/TransitiveDep-extra1.cps
  13. 9 0
      Tests/FindPackageCpsTest/cps/TransitiveDep-extra2.cps
  14. 12 0
      Tests/FindPackageCpsTest/cps/TransitiveDep-extra3.cps
  15. 10 0
      Tests/FindPackageCpsTest/cps/TransitiveDep.cps
  16. 11 0
      Tests/FindPackageCpsTest/cps/TransitiveTest.cps
  17. 1 0
      Tests/RunCMake/find_package/MissingComponent-result.txt
  18. 11 0
      Tests/RunCMake/find_package/MissingComponent-stderr.txt
  19. 19 0
      Tests/RunCMake/find_package/MissingComponent.cmake
  20. 1 0
      Tests/RunCMake/find_package/MissingComponentDependency-result.txt
  21. 22 0
      Tests/RunCMake/find_package/MissingComponentDependency-stderr.txt
  22. 19 0
      Tests/RunCMake/find_package/MissingComponentDependency.cmake
  23. 1 0
      Tests/RunCMake/find_package/MissingTransitiveComponent-result.txt
  24. 17 0
      Tests/RunCMake/find_package/MissingTransitiveComponent-stderr.txt
  25. 19 0
      Tests/RunCMake/find_package/MissingTransitiveComponent.cmake
  26. 1 0
      Tests/RunCMake/find_package/MissingTransitiveComponentDependency-result.txt
  27. 24 0
      Tests/RunCMake/find_package/MissingTransitiveComponentDependency-stderr.txt
  28. 20 0
      Tests/RunCMake/find_package/MissingTransitiveComponentDependency.cmake
  29. 4 0
      Tests/RunCMake/find_package/RunCMakeTest.cmake
  30. 12 0
      Tests/RunCMake/find_package/cps/componenttest-extra.cps
  31. 6 0
      Tests/RunCMake/find_package/cps/componenttest.cps
  32. 11 0
      Tests/RunCMake/find_package/cps/transitiveincomplete.cps
  33. 11 0
      Tests/RunCMake/find_package/cps/transitivemissing.cps

+ 3 - 3
Help/command/find_package.rst

@@ -88,9 +88,9 @@ The command has a few modes by which it searches for packages:
     mode", "package configuration files", and so forth refer equally to both
     mode", "package configuration files", and so forth refer equally to both
     CPS and CMake-script files.  However, some features of ``find_package``
     CPS and CMake-script files.  However, some features of ``find_package``
     are not supported at this time when a CPS file is found.  In particular,
     are not supported at this time when a CPS file is found.  In particular,
-    no attempt to validate whether a candidate ``.cps`` file matches
-    ``VERSION`` or ``COMPONENTS`` requirements is performed at this time.
-    (We expect to implement these features in the near future.)
+    if a ``VERSION`` requirement is specified, only ``.cps`` files which do not
+    provide version information will be rejected.  (We expect to implement
+    proper version validation in the near future.)
 
 
     Search is implemented in a manner that will tend to prefer |CPS| files
     Search is implemented in a manner that will tend to prefer |CPS| files
     over CMake-script config files in most cases.  Specifying ``CONFIGS``
     over CMake-script config files in most cases.  Specifying ``CONFIGS``

+ 101 - 36
Source/cmFindPackageCommand.cxx

@@ -1846,7 +1846,7 @@ bool cmFindPackageCommand::FindEnvironmentConfig()
 }
 }
 
 
 cmFindPackageCommand::AppendixMap cmFindPackageCommand::FindAppendices(
 cmFindPackageCommand::AppendixMap cmFindPackageCommand::FindAppendices(
-  std::string const& base) const
+  std::string const& base, cmPackageInfoReader const& baseReader) const
 {
 {
   AppendixMap appendices;
   AppendixMap appendices;
 
 
@@ -1865,9 +1865,11 @@ cmFindPackageCommand::AppendixMap cmFindPackageCommand::FindAppendices(
       }
       }
 
 
       std::unique_ptr<cmPackageInfoReader> reader =
       std::unique_ptr<cmPackageInfoReader> reader =
-        cmPackageInfoReader::Read(extra, this->CpsReader.get());
+        cmPackageInfoReader::Read(extra, &baseReader);
       if (reader && reader->GetName() == this->Name) {
       if (reader && reader->GetName() == this->Name) {
-        appendices.emplace(extra, std::move(reader));
+        std::vector<std::string> components = reader->GetComponentNames();
+        Appendix appendix{ std::move(reader), std::move(components) };
+        appendices.emplace(extra, std::move(appendix));
       }
       }
     }
     }
   }
   }
@@ -1894,27 +1896,55 @@ bool cmFindPackageCommand::ReadListFile(std::string const& f,
 
 
 bool cmFindPackageCommand::ReadPackage()
 bool cmFindPackageCommand::ReadPackage()
 {
 {
-  // Resolve any transitive dependencies.
+  // Resolve any transitive dependencies for the root file.
   if (!FindPackageDependencies(this->FileFound, *this->CpsReader,
   if (!FindPackageDependencies(this->FileFound, *this->CpsReader,
                                this->Required)) {
                                this->Required)) {
     return false;
     return false;
   }
   }
 
 
+  auto const hasComponentsRequested =
+    !this->RequiredComponents.empty() || !this->OptionalComponents.empty();
+
   cmMakefile::CallRAII scope{ this->Makefile, this->FileFound, this->Status };
   cmMakefile::CallRAII scope{ this->Makefile, this->FileFound, this->Status };
 
 
-  // Locate appendices.
-  cmFindPackageCommand::AppendixMap appendices =
-    this->FindAppendices(this->FileFound);
+  // Loop over appendices.
+  auto iter = this->CpsAppendices.begin();
+  while (iter != this->CpsAppendices.end()) {
+    bool required = false;
+    bool important = false;
+
+    // Check if this appendix provides any requested components.
+    if (hasComponentsRequested) {
+      auto providesAny = [&iter](
+                           std::set<std::string> const& desiredComponents) {
+        return std::any_of(iter->second.Components.begin(),
+                           iter->second.Components.end(),
+                           [&desiredComponents](std::string const& component) {
+                             return cm::contains(desiredComponents, component);
+                           });
+      };
+
+      if (providesAny(this->RequiredComponents)) {
+        important = true;
+        required = this->Required;
+      } else if (!providesAny(this->OptionalComponents)) {
+        // This appendix doesn't provide any requested components; remove it
+        // from the set to be imported.
+        iter = this->CpsAppendices.erase(iter);
+        continue;
+      }
+    }
 
 
-  auto iter = appendices.begin();
-  while (iter != appendices.end()) {
-    bool providesRequiredComponents = false; // TODO
-    bool required = providesRequiredComponents && this->Required;
-    if (!this->FindPackageDependencies(iter->first, *iter->second, required)) {
-      if (providesRequiredComponents) {
+    // Resolve any transitive dependencies for the appendix.
+    if (!this->FindPackageDependencies(iter->first, iter->second, required)) {
+      if (important) {
+        // Some dependencies are missing, and we need(ed) this appendix; fail.
         return false;
         return false;
       }
       }
-      iter = appendices.erase(iter);
+
+      // Some dependencies are missing, but we don't need this appendix; remove
+      // it from the set to be imported.
+      iter = this->CpsAppendices.erase(iter);
     } else {
     } else {
       ++iter;
       ++iter;
     }
     }
@@ -1926,10 +1956,11 @@ bool cmFindPackageCommand::ReadPackage()
   }
   }
 
 
   // Import targets from appendices.
   // Import targets from appendices.
-  for (auto const& appendix : appendices) {
+  // NOLINTNEXTLINE(readability-use-anyofallof)
+  for (auto const& appendix : this->CpsAppendices) {
     cmMakefile::CallRAII appendixScope{ this->Makefile, appendix.first,
     cmMakefile::CallRAII appendixScope{ this->Makefile, appendix.first,
                                         this->Status };
                                         this->Status };
-    if (!this->ImportPackageTargets(appendix.first, *appendix.second)) {
+    if (!this->ImportPackageTargets(appendix.first, appendix.second)) {
       return false;
       return false;
     }
     }
   }
   }
@@ -2689,29 +2720,63 @@ bool cmFindPackageCommand::CheckVersion(std::string const& config_file)
       cm::optional<std::string> cpsVersion = reader->GetVersion();
       cm::optional<std::string> cpsVersion = reader->GetVersion();
       if (cpsVersion) {
       if (cpsVersion) {
         // TODO: Implement version check for CPS
         // TODO: Implement version check for CPS
-        this->VersionFound = (version = std::move(*cpsVersion));
+        result = true;
+      } else {
+        result = this->Version.empty();
+      }
 
 
-        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;
+      if (result) {
+        // Locate appendices.
+        cmFindPackageCommand::AppendixMap appendices =
+          this->FindAppendices(config_file, *reader);
+
+        // Collect available components.
+        std::set<std::string> allComponents;
+
+        std::vector<std::string> const& rootComponents =
+          reader->GetComponentNames();
+        allComponents.insert(rootComponents.begin(), rootComponents.end());
+
+        for (auto const& appendix : appendices) {
+          allComponents.insert(appendix.second.Components.begin(),
+                               appendix.second.Components.end());
+        }
+
+        // Verify that all required components are available.
+        std::vector<std::string> missingComponents;
+        std::set_difference(this->RequiredComponents.begin(),
+                            this->RequiredComponents.end(),
+                            allComponents.begin(), allComponents.end(),
+                            std::back_inserter(missingComponents));
+        if (!missingComponents.empty()) {
+          result = false;
         }
         }
+
+        if (result && cpsVersion) {
+          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);
+        this->CpsAppendices = std::move(appendices);
       }
       }
-      this->CpsReader = std::move(reader);
-      result = true;
     }
     }
   } else {
   } else {
     // Get the filename without the .cmake extension.
     // Get the filename without the .cmake extension.

+ 11 - 3
Source/cmFindPackageCommand.h

@@ -141,9 +141,16 @@ private:
   bool ReadListFile(std::string const& f, PolicyScopeRule psr);
   bool ReadListFile(std::string const& f, PolicyScopeRule psr);
   bool ReadPackage();
   bool ReadPackage();
 
 
-  using AppendixMap =
-    std::map<std::string, std::unique_ptr<cmPackageInfoReader>>;
-  AppendixMap FindAppendices(std::string const& base) const;
+  struct Appendix
+  {
+    std::unique_ptr<cmPackageInfoReader> Reader;
+    std::vector<std::string> Components;
+
+    operator cmPackageInfoReader&() const { return *this->Reader; }
+  };
+  using AppendixMap = std::map<std::string, Appendix>;
+  AppendixMap FindAppendices(std::string const& base,
+                             cmPackageInfoReader const& baseReader) const;
   bool FindPackageDependencies(std::string const& fileName,
   bool FindPackageDependencies(std::string const& fileName,
                                cmPackageInfoReader const& reader,
                                cmPackageInfoReader const& reader,
                                bool required);
                                bool required);
@@ -299,6 +306,7 @@ private:
   std::vector<ConfigFileInfo> ConsideredConfigs;
   std::vector<ConfigFileInfo> ConsideredConfigs;
 
 
   std::unique_ptr<cmPackageInfoReader> CpsReader;
   std::unique_ptr<cmPackageInfoReader> CpsReader;
+  AppendixMap CpsAppendices;
 
 
   friend struct std::hash<ConfigFileInfo>;
   friend struct std::hash<ConfigFileInfo>;
 };
 };

+ 12 - 0
Source/cmPackageInfoReader.cxx

@@ -481,6 +481,18 @@ std::vector<cmPackageRequirement> cmPackageInfoReader::GetRequirements() const
   return requirements;
   return requirements;
 }
 }
 
 
+std::vector<std::string> cmPackageInfoReader::GetComponentNames() const
+{
+  std::vector<std::string> componentNames;
+
+  Json::Value const& components = this->Data["components"];
+  for (auto ci = components.begin(), ce = components.end(); ci != ce; ++ci) {
+    componentNames.emplace_back(ci.name());
+  }
+
+  return componentNames;
+}
+
 std::string cmPackageInfoReader::ResolvePath(std::string path) const
 std::string cmPackageInfoReader::ResolvePath(std::string path) const
 {
 {
   cmSystemTools::ConvertToUnixSlashes(path);
   cmSystemTools::ConvertToUnixSlashes(path);

+ 1 - 0
Source/cmPackageInfoReader.h

@@ -50,6 +50,7 @@ public:
   std::vector<unsigned> ParseVersion() const;
   std::vector<unsigned> ParseVersion() const;
 
 
   std::vector<cmPackageRequirement> GetRequirements() const;
   std::vector<cmPackageRequirement> GetRequirements() const;
+  std::vector<std::string> GetComponentNames() const;
 
 
   /// Create targets for components specified in the CPS file.
   /// Create targets for components specified in the CPS file.
   bool ImportTargets(cmMakefile* makefile, cmExecutionStatus& status);
   bool ImportTargets(cmMakefile* makefile, cmExecutionStatus& status);

+ 46 - 0
Tests/FindPackageCpsTest/CMakeLists.txt

@@ -185,3 +185,49 @@ elseif(NOT TARGET Bar::Target1)
 elseif(NOT TARGET Bar::Target2)
 elseif(NOT TARGET Bar::Target2)
   message(SEND_ERROR "Bar::Target2 missing !")
   message(SEND_ERROR "Bar::Target2 missing !")
 endif()
 endif()
+
+###############################################################################
+# Test requesting components from a package.
+
+find_package(ComponentTest
+  COMPONENTS Target1 Target2
+  OPTIONAL_COMPONENTS Target4 Target6)
+if(NOT ComponentTest_FOUND)
+  message(SEND_ERROR "ComponentTest not found !")
+elseif(NOT TARGET ComponentTest::Target1)
+  message(SEND_ERROR "ComponentTest::Target1 missing !")
+elseif(NOT TARGET ComponentTest::Target2)
+  message(SEND_ERROR "ComponentTest::Target2 missing !")
+elseif(NOT TARGET ComponentTest::Target3)
+  message(SEND_ERROR "ComponentTest::Target3 missing !")
+elseif(NOT TARGET ComponentTest::Target4)
+  message(SEND_ERROR "ComponentTest::Target4 missing !")
+elseif(NOT TARGET ComponentTest::Target5)
+  message(SEND_ERROR "ComponentTest::Target5 missing !")
+elseif(TARGET ComponentTest::Target6)
+  message(SEND_ERROR "ComponentTest::Target6 exists ?!")
+elseif(TARGET ComponentTest::Target7)
+  message(SEND_ERROR "ComponentTest::Target7 exists ?!")
+elseif(TARGET ComponentTest::Target8)
+  message(SEND_ERROR "ComponentTest::Target8 exists ?!")
+endif()
+
+###############################################################################
+# Test requesting components from a dependency.
+
+find_package(TransitiveTest)
+if(NOT TransitiveTest_FOUND)
+  message(SEND_ERROR "TransitiveTest not found !")
+elseif(NOT TransitiveDep_FOUND)
+  message(SEND_ERROR "TransitiveTest's TransitiveDep not found !")
+elseif(NOT TARGET TransitiveDep::Target1)
+  message(SEND_ERROR "TransitiveDep::Target1 missing !")
+elseif(NOT TARGET TransitiveDep::Target2)
+  message(SEND_ERROR "TransitiveDep::Target2 missing !")
+elseif(NOT TARGET TransitiveDep::Target3)
+  message(SEND_ERROR "TransitiveDep::Target3 missing !")
+elseif(TARGET TransitiveDep::Target4)
+  message(SEND_ERROR "TransitiveDep::Target4 exists ?!")
+elseif(TARGET TransitiveDep::Target5)
+  message(SEND_ERROR "TransitiveDep::Target5 exists ?!")
+endif()

+ 12 - 0
Tests/FindPackageCpsTest/cps/ComponentTest-extra1.cps

@@ -0,0 +1,12 @@
+{
+  "cps_version": "0.13",
+  "name": "ComponentTest",
+  "components": {
+    "Target2": {
+      "type": "interface"
+    },
+    "Target3": {
+      "type": "interface"
+    }
+  }
+}

+ 12 - 0
Tests/FindPackageCpsTest/cps/ComponentTest-extra2.cps

@@ -0,0 +1,12 @@
+{
+  "cps_version": "0.13",
+  "name": "ComponentTest",
+  "components": {
+    "Target4": {
+      "type": "interface"
+    },
+    "Target5": {
+      "type": "interface"
+    }
+  }
+}

+ 15 - 0
Tests/FindPackageCpsTest/cps/ComponentTest-extra3.cps

@@ -0,0 +1,15 @@
+{
+  "cps_version": "0.13",
+  "name": "ComponentTest",
+  "requires": {
+    "DoesNotExist": null
+  },
+  "components": {
+    "Target6": {
+      "type": "interface"
+    },
+    "Target7": {
+      "type": "interface"
+    }
+  }
+}

+ 9 - 0
Tests/FindPackageCpsTest/cps/ComponentTest-extra4.cps

@@ -0,0 +1,9 @@
+{
+  "cps_version": "0.13",
+  "name": "ComponentTest",
+  "components": {
+    "Target8": {
+      "type": "interface"
+    }
+  }
+}

+ 10 - 0
Tests/FindPackageCpsTest/cps/ComponentTest.cps

@@ -0,0 +1,10 @@
+{
+  "cps_version": "0.13",
+  "name": "ComponentTest",
+  "cps_path": "@prefix@/cps",
+  "components": {
+    "Target1": {
+      "type": "interface"
+    }
+  }
+}

+ 12 - 0
Tests/FindPackageCpsTest/cps/TransitiveDep-extra1.cps

@@ -0,0 +1,12 @@
+{
+  "cps_version": "0.13",
+  "name": "TransitiveDep",
+  "components": {
+    "Target2": {
+      "type": "interface"
+    },
+    "Target3": {
+      "type": "interface"
+    }
+  }
+}

+ 9 - 0
Tests/FindPackageCpsTest/cps/TransitiveDep-extra2.cps

@@ -0,0 +1,9 @@
+{
+  "cps_version": "0.13",
+  "name": "TransitiveDep",
+  "components": {
+    "Target4": {
+      "type": "interface"
+    }
+  }
+}

+ 12 - 0
Tests/FindPackageCpsTest/cps/TransitiveDep-extra3.cps

@@ -0,0 +1,12 @@
+{
+  "cps_version": "0.13",
+  "name": "ComponentTest",
+  "requires": {
+    "DoesNotExist": null
+  },
+  "components": {
+    "Target5": {
+      "type": "interface"
+    }
+  }
+}

+ 10 - 0
Tests/FindPackageCpsTest/cps/TransitiveDep.cps

@@ -0,0 +1,10 @@
+{
+  "cps_version": "0.13",
+  "name": "TransitiveDep",
+  "cps_path": "@prefix@/cps",
+  "components": {
+    "Target1": {
+      "type": "interface"
+    }
+  }
+}

+ 11 - 0
Tests/FindPackageCpsTest/cps/TransitiveTest.cps

@@ -0,0 +1,11 @@
+{
+  "cps_version": "0.13",
+  "name": "TransitiveTest",
+  "cps_path": "@prefix@/cps",
+  "requires": {
+    "TransitiveDep": {
+      "components": [ "Target2" ]
+    }
+  },
+  "components": {}
+}

+ 1 - 0
Tests/RunCMake/find_package/MissingComponent-result.txt

@@ -0,0 +1 @@
+1

+ 11 - 0
Tests/RunCMake/find_package/MissingComponent-stderr.txt

@@ -0,0 +1,11 @@
+CMake Error at MissingComponent.cmake:[0-9]+ \(find_package\):
+  Could not find a configuration file for package "ComponentTest" that is
+  compatible with requested version ""\.
+
+  The following configuration files were considered but not accepted:
+(
+    [^
+]*/Tests/RunCMake/find_package/cps/[Cc]omponent[Tt]est\.cps, version: unknown)+
+
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 19 - 0
Tests/RunCMake/find_package/MissingComponent.cmake

@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 3.31)
+
+set(CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES "e82e467b-f997-4464-8ace-b00808fff261")
+
+# 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)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+
+###############################################################################
+# Test requesting unavailable components from a package.
+find_package(ComponentTest REQUIRED COMPONENTS DoesNotExist)

+ 1 - 0
Tests/RunCMake/find_package/MissingComponentDependency-result.txt

@@ -0,0 +1 @@
+1

+ 22 - 0
Tests/RunCMake/find_package/MissingComponentDependency-stderr.txt

@@ -0,0 +1,22 @@
+CMake Error in cps/componenttest-extra\.cps:
+  Could not find a package configuration file provided by "DoesNotExist" with
+  any of the following names:
+
+    DoesNotExist\.cps
+    doesnotexist\.cps
+    DoesNotExistConfig\.cmake
+    doesnotexist-config\.cmake
+
+  Add the installation prefix of "DoesNotExist" to CMAKE_PREFIX_PATH or set
+  "DoesNotExist_DIR" to a directory containing one of the above files\.  If
+  "DoesNotExist" provides a separate development package or SDK, be sure it
+  has been installed\.
+Call Stack \(most recent call first\):
+  cps/[Cc]omponent[Tt]est\.cps
+  MissingComponentDependency\.cmake:[0-9]+ \(find_package\)
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at MissingComponentDependency.cmake:[0-9]+ \(find_package\):
+  find_package could not find DoesNotExist, required by ComponentTest\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 19 - 0
Tests/RunCMake/find_package/MissingComponentDependency.cmake

@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 3.31)
+
+set(CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES "e82e467b-f997-4464-8ace-b00808fff261")
+
+# 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)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+
+###############################################################################
+# Test requesting components with missing dependencies from a package.
+find_package(ComponentTest REQUIRED COMPONENTS Incomplete)

+ 1 - 0
Tests/RunCMake/find_package/MissingTransitiveComponent-result.txt

@@ -0,0 +1 @@
+1

+ 17 - 0
Tests/RunCMake/find_package/MissingTransitiveComponent-stderr.txt

@@ -0,0 +1,17 @@
+CMake Error in cps/[Tt]ransitive[Mm]issing\.cps:
+  Could not find a configuration file for package "ComponentTest" that is
+  compatible with requested version ""\.
+
+  The following configuration files were considered but not accepted:
+(
+    [^
+]*/Tests/RunCMake/find_package/cps/[Cc]omponent[Tt]est\.cps, version: unknown)+
+
+Call Stack \(most recent call first\):
+  MissingTransitiveComponent\.cmake:[0-9]+ \(find_package\)
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at MissingTransitiveComponent\.cmake:[0-9]+ \(find_package\):
+  find_package could not find ComponentTest, required by TransitiveMissing\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 19 - 0
Tests/RunCMake/find_package/MissingTransitiveComponent.cmake

@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 3.31)
+
+set(CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES "e82e467b-f997-4464-8ace-b00808fff261")
+
+# 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)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+
+###############################################################################
+# Test depending on components of another package which are unavailable.
+find_package(TransitiveMissing REQUIRED)

+ 1 - 0
Tests/RunCMake/find_package/MissingTransitiveComponentDependency-result.txt

@@ -0,0 +1 @@
+1

+ 24 - 0
Tests/RunCMake/find_package/MissingTransitiveComponentDependency-stderr.txt

@@ -0,0 +1,24 @@
+CMake Error in cps/componenttest-extra\.cps:
+  Could not find a package configuration file provided by "DoesNotExist" with
+  any of the following names:
+
+    DoesNotExist\.cps
+    doesnotexist\.cps
+    DoesNotExistConfig\.cmake
+    doesnotexist-config\.cmake
+
+  Add the installation prefix of "DoesNotExist" to CMAKE_PREFIX_PATH or set
+  "DoesNotExist_DIR" to a directory containing one of the above files\.  If
+  "DoesNotExist" provides a separate development package or SDK, be sure it
+  has been installed\.
+Call Stack \(most recent call first\):
+  cps/[Cc]omponent[Tt]est\.cps
+  cps/[Tt]ransitive[Ii]ncomplete\.cps
+  MissingTransitiveComponentDependency\.cmake:[0-9]+ \(find_package\)
+  CMakeLists\.txt:[0-9]+ \(include\)
++
+CMake Error at MissingTransitiveComponentDependency\.cmake:[0-9]+ \(find_package\):
+  find_package could not find ComponentTest, required by
+  TransitiveIncomplete\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)

+ 20 - 0
Tests/RunCMake/find_package/MissingTransitiveComponentDependency.cmake

@@ -0,0 +1,20 @@
+cmake_minimum_required(VERSION 3.31)
+
+set(CMAKE_EXPERIMENTAL_FIND_CPS_PACKAGES "e82e467b-f997-4464-8ace-b00808fff261")
+
+# 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)
+
+set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+
+###############################################################################
+# Test depending on components of another package which are missing
+# dependencies.
+find_package(TransitiveIncomplete REQUIRED)

+ 4 - 0
Tests/RunCMake/find_package/RunCMakeTest.cmake

@@ -23,6 +23,10 @@ run_cmake(MissingConfigOneName)
 run_cmake(MissingConfigRequired)
 run_cmake(MissingConfigRequired)
 run_cmake(MissingConfigVersion)
 run_cmake(MissingConfigVersion)
 run_cmake(MissingTransitiveDependency)
 run_cmake(MissingTransitiveDependency)
+run_cmake(MissingComponent)
+run_cmake(MissingComponentDependency)
+run_cmake(MissingTransitiveComponent)
+run_cmake(MissingTransitiveComponentDependency)
 run_cmake(MixedModeOptions)
 run_cmake(MixedModeOptions)
 run_cmake_with_options(ModuleModeDebugPkg --debug-find-pkg=Foo,Zot)
 run_cmake_with_options(ModuleModeDebugPkg --debug-find-pkg=Foo,Zot)
 run_cmake(PackageRoot)
 run_cmake(PackageRoot)

+ 12 - 0
Tests/RunCMake/find_package/cps/componenttest-extra.cps

@@ -0,0 +1,12 @@
+{
+  "cps_version": "0.13",
+  "name": "ComponentTest",
+  "requires": {
+    "DoesNotExist": null
+  },
+  "components": {
+    "Incomplete": {
+      "type": "interface"
+    }
+  }
+}

+ 6 - 0
Tests/RunCMake/find_package/cps/componenttest.cps

@@ -0,0 +1,6 @@
+{
+  "cps_version": "0.13",
+  "name": "ComponentTest",
+  "cps_path": "@prefix@/cps",
+  "components": {}
+}

+ 11 - 0
Tests/RunCMake/find_package/cps/transitiveincomplete.cps

@@ -0,0 +1,11 @@
+{
+  "cps_version": "0.13",
+  "name": "TransitiveIncomplete",
+  "cps_path": "@prefix@/cps",
+  "requires": {
+    "ComponentTest": {
+      "components": [ "Incomplete" ]
+    }
+  },
+  "components": {}
+}

+ 11 - 0
Tests/RunCMake/find_package/cps/transitivemissing.cps

@@ -0,0 +1,11 @@
+{
+  "cps_version": "0.13",
+  "name": "TransitiveMissing",
+  "cps_path": "@prefix@/cps",
+  "requires": {
+    "ComponentTest": {
+      "components": [ "DoesNotExist" ]
+    }
+  },
+  "components": {}
+}