Ver Fonte

CPS: Support FILE_SET HEADERS

Fixes: #26806
Vito Gamberini há 5 meses atrás
pai
commit
7db44fbfb8

+ 11 - 0
Source/cmExportBuildPackageInfoGenerator.cxx

@@ -3,6 +3,7 @@
 #include "cmExportBuildPackageInfoGenerator.h"
 
 #include <cassert>
+#include <map>
 #include <utility>
 #include <vector>
 
@@ -10,7 +11,9 @@
 
 #include <cm3p/json/value.h>
 
+#include "cmAlgorithms.h"
 #include "cmGeneratorExpression.h"
+#include "cmList.h"
 #include "cmPackageInfoArguments.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
@@ -68,6 +71,14 @@ bool cmExportBuildPackageInfoGenerator::GenerateMainFile(std::ostream& os)
       }
     }
 
+    // De-duplicate include directories prior to generation.
+    auto it = properties.find("INTERFACE_INCLUDE_DIRECTORIES");
+    if (it != properties.end()) {
+      auto list = cmList{ it->second };
+      list = cmList{ list.begin(), cmRemoveDuplicates(list) };
+      properties["INTERFACE_INCLUDE_DIRECTORIES"] = list.to_string();
+    }
+
     // Set configuration-agnostic properties for component.
     this->GenerateInterfaceProperties(*component, target, properties);
   }

+ 112 - 10
Source/cmExportInstallPackageInfoGenerator.cxx

@@ -2,19 +2,32 @@
    file LICENSE.rst or https://cmake.org/licensing for details.  */
 #include "cmExportInstallPackageInfoGenerator.h"
 
+#include <map>
 #include <memory>
 #include <set>
+#include <sstream>
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
+#include <cm/string_view>
+#include <cmext/algorithm>
+#include <cmext/string_view>
+
 #include <cm3p/json/value.h>
 
+#include "cmAlgorithms.h"
 #include "cmExportSet.h"
+#include "cmFileSet.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorTarget.h"
 #include "cmInstallExportGenerator.h"
+#include "cmInstallFileSetGenerator.h"
+#include "cmList.h"
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmOutputConverter.h"
 #include "cmPackageInfoArguments.h"
 #include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
@@ -67,7 +80,6 @@ bool cmExportInstallPackageInfoGenerator::GenerateMainFile(std::ostream& os)
   root["cps_path"] = packagePath;
 
   // Create all the imported targets.
-  bool requiresConfigFiles = false;
   for (cmTargetExport const* te : allTargets) {
     cmGeneratorTarget* gt = te->Target;
     cmStateEnums::TargetType targetType = this->GetExportTargetType(te);
@@ -86,11 +98,22 @@ bool cmExportInstallPackageInfoGenerator::GenerateMainFile(std::ostream& os)
       gt, cmGeneratorExpression::InstallInterface, properties);
 
     if (targetType != cmStateEnums::INTERFACE_LIBRARY) {
-      requiresConfigFiles = true;
+      this->RequiresConfigFiles = true;
+    }
+
+    // De-duplicate include directories prior to generation.
+    auto it = properties.find("INTERFACE_INCLUDE_DIRECTORIES");
+    if (it != properties.end()) {
+      auto list = cmList{ it->second };
+      list = cmList{ list.begin(), cmRemoveDuplicates(list) };
+      properties["INTERFACE_INCLUDE_DIRECTORIES"] = list.to_string();
     }
 
     // Set configuration-agnostic properties for component.
     this->GenerateInterfaceProperties(*component, gt, properties);
+    if (!this->GenerateFileSetProperties(*component, gt, te)) {
+      return false;
+    }
   }
 
   this->GeneratePackageRequires(root);
@@ -101,7 +124,7 @@ bool cmExportInstallPackageInfoGenerator::GenerateMainFile(std::ostream& os)
   bool result = true;
 
   // Generate an import file for each configuration.
-  if (requiresConfigFiles) {
+  if (this->RequiresConfigFiles) {
     for (std::string const& c : this->Configurations) {
       if (!this->GenerateImportFileConfig(c)) {
         result = false;
@@ -123,19 +146,19 @@ void cmExportInstallPackageInfoGenerator::GenerateImportTargetsConfig(
 
   for (auto const& te : this->GetExportSet()->GetTargetExports()) {
     // Collect import properties for this target.
-    if (this->GetExportTargetType(te.get()) ==
-        cmStateEnums::INTERFACE_LIBRARY) {
-      continue;
-    }
-
     ImportPropertyMap properties;
     std::set<std::string> importedLocations;
 
-    this->PopulateImportProperties(config, suffix, te.get(), properties,
-                                   importedLocations);
+    if (this->GetExportTargetType(te.get()) !=
+        cmStateEnums::INTERFACE_LIBRARY) {
+      this->PopulateImportProperties(config, suffix, te.get(), properties,
+                                     importedLocations);
+    }
 
     Json::Value component =
       this->GenerateInterfaceConfigProperties(suffix, properties);
+    this->GenerateFileSetProperties(component, te->Target, te.get(), config);
+
     if (!component.empty()) {
       components[te->Target->GetExportName()] = std::move(component);
     }
@@ -193,3 +216,82 @@ std::string cmExportInstallPackageInfoGenerator::GetCxxModulesDirectory() const
   // return IEGen->GetCxxModuleDirectory();
   return {};
 }
+
+cm::optional<std::string>
+cmExportInstallPackageInfoGenerator::GetFileSetDirectory(
+  cmGeneratorTarget* gte, cmTargetExport const* te, cmFileSet* fileSet,
+  cm::optional<std::string> const& config)
+{
+  cmGeneratorExpression ge(*gte->Makefile->GetCMakeInstance());
+  auto cge =
+    ge.Parse(te->FileSetGenerators.at(fileSet->GetName())->GetDestination());
+
+  std::string const unescapedDest =
+    cge->Evaluate(gte->LocalGenerator, config.value_or(""), gte);
+  bool const isConfigDependent = cge->GetHadContextSensitiveCondition();
+
+  if (config && !isConfigDependent) {
+    return {};
+  }
+  if (!config && isConfigDependent) {
+    this->RequiresConfigFiles = true;
+    return {};
+  }
+
+  std::string const& type = fileSet->GetType();
+  if (config && (type == "CXX_MODULES"_s)) {
+    // C++ modules do not support interface file sets which are dependent
+    // upon the configuration.
+    cmMakefile* mf = gte->LocalGenerator->GetMakefile();
+    std::ostringstream e;
+    e << "The \"" << gte->GetName() << "\" target's interface file set \""
+      << fileSet->GetName() << "\" of type \"" << type
+      << "\" contains context-sensitive base file entries which is not "
+         "supported.";
+    mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+    return {};
+  }
+
+  cm::optional<std::string> dest = cmOutputConverter::EscapeForCMake(
+    unescapedDest, cmOutputConverter::WrapQuotes::NoWrap);
+
+  if (!cmSystemTools::FileIsFullPath(unescapedDest)) {
+    dest = cmStrCat("@prefix@/"_s, *dest);
+  }
+
+  return dest;
+}
+
+bool cmExportInstallPackageInfoGenerator::GenerateFileSetProperties(
+  Json::Value& component, cmGeneratorTarget* gte, cmTargetExport const* te,
+  cm::optional<std::string> config)
+{
+  std::set<std::string> seenIncludeDirectories;
+  for (auto const& name : gte->Target->GetAllInterfaceFileSets()) {
+    cmFileSet* fileSet = gte->Target->GetFileSet(name);
+
+    if (!fileSet) {
+      gte->Makefile->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmStrCat("File set \"", name,
+                 "\" is listed in interface file sets of ", gte->GetName(),
+                 " but has not been created"));
+      return false;
+    }
+
+    cm::optional<std::string> const& fileSetDirectory =
+      this->GetFileSetDirectory(gte, te, fileSet, config);
+
+    if (fileSet->GetType() == "HEADERS"_s) {
+      if (fileSetDirectory &&
+          !cm::contains(seenIncludeDirectories, *fileSetDirectory)) {
+        component["includes"].append(*fileSetDirectory);
+        seenIncludeDirectories.insert(*fileSetDirectory);
+      }
+    } else if (fileSet->GetType() == "CXX_MODULES"_s) {
+      /* TODO: Handle the CXX_MODULE directory */
+    }
+  }
+
+  return true;
+}

+ 19 - 0
Source/cmExportInstallPackageInfoGenerator.h

@@ -7,12 +7,20 @@
 #include <iosfwd>
 #include <string>
 
+#include <cm/optional>
+
 #include "cmExportInstallFileGenerator.h"
 #include "cmExportPackageInfoGenerator.h"
 
+class cmFileSet;
 class cmGeneratorTarget;
 class cmInstallExportGenerator;
 class cmPackageInfoArguments;
+class cmTargetExport;
+
+namespace Json {
+class Value;
+}
 
 /** \class cmExportInstallPackageInfoGenerator
  * \brief Generate files exporting targets from an install tree.
@@ -43,6 +51,8 @@ public:
   std::string GetConfigImportFileGlob() const override;
 
 protected:
+  bool RequiresConfigFiles = false;
+
   std::string const& GetExportName() const override;
 
   // Implement virtual methods from the superclass.
@@ -60,4 +70,13 @@ protected:
 
   std::string GetCxxModulesDirectory() const override;
   // TODO: Generate C++ module info in a not-CMake-specific format.
+
+  cm::optional<std::string> GetFileSetDirectory(
+    cmGeneratorTarget* gte, cmTargetExport const* te, cmFileSet* fileSet,
+    cm::optional<std::string> const& config = {});
+
+  bool GenerateFileSetProperties(Json::Value& component,
+                                 cmGeneratorTarget* gte,
+                                 cmTargetExport const* te,
+                                 cm::optional<std::string> config = {});
 };

+ 25 - 4
Tests/RunCMake/CpsExportImportBuild/TestLibrary.cmake

@@ -1,12 +1,33 @@
 project(TestLibrary C)
 
-add_library(liba SHARED liba.c)
-add_library(libb SHARED libb.c)
+add_library(liba SHARED)
+target_sources(liba
+  PRIVATE
+    liba/liba.c
+  INTERFACE
+    FILE_SET HEADERS
+    BASE_DIRS
+      liba
+    FILES
+      liba/liba.h
+)
+
+add_library(libb SHARED)
+target_sources(libb
+  PRIVATE
+    libb/libb.c
+  INTERFACE
+    FILE_SET HEADERS
+    BASE_DIRS
+      libb
+    FILES
+      libb/libb.h
+)
 
 target_link_libraries(libb PUBLIC liba)
 
-install(TARGETS liba EXPORT liba DESTINATION lib)
+install(TARGETS liba EXPORT liba FILE_SET HEADERS)
 export(EXPORT liba PACKAGE_INFO liba)
 
-install(TARGETS libb EXPORT libb DESTINATION lib)
+install(TARGETS libb EXPORT libb FILE_SET HEADERS)
 export(EXPORT libb PACKAGE_INFO libb)

+ 1 - 6
Tests/RunCMake/CpsExportImportBuild/app.c

@@ -1,11 +1,6 @@
+#include <libb.h>
 #include <stdio.h>
 
-extern
-#ifdef _WIN32
-__declspec(dllimport)
-#endif
-int ask(void);
-
 int main(void)
 {
   printf("%i\n", ask());

+ 0 - 0
Tests/RunCMake/CpsExportImportBuild/liba.c → Tests/RunCMake/CpsExportImportBuild/liba/liba.c


+ 3 - 0
Tests/RunCMake/CpsExportImportBuild/liba/liba.h

@@ -0,0 +1,3 @@
+#pragma once
+
+int answer(void);

+ 1 - 5
Tests/RunCMake/CpsExportImportBuild/libb.c → Tests/RunCMake/CpsExportImportBuild/libb/libb.c

@@ -1,8 +1,4 @@
-extern
-#ifdef _WIN32
-__declspec(dllimport)
-#endif
-int answer(void);
+#include <liba.h>
 
 #ifdef _WIN32
 __declspec(dllexport)

+ 3 - 0
Tests/RunCMake/CpsExportImportBuild/libb/libb.h

@@ -0,0 +1,3 @@
+#pragma once
+
+int ask(void);

+ 25 - 4
Tests/RunCMake/CpsExportImportInstall/TestLibrary.cmake

@@ -2,13 +2,34 @@ project(TestLibrary C)
 
 set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/../install")
 
-add_library(liba SHARED liba.c)
-add_library(libb SHARED libb.c)
+add_library(liba SHARED)
+target_sources(liba
+  PRIVATE
+    liba/liba.c
+  INTERFACE
+    FILE_SET HEADERS
+    BASE_DIRS
+      liba
+    FILES
+      liba/liba.h
+)
+
+add_library(libb SHARED)
+target_sources(libb
+  PRIVATE
+    libb/libb.c
+  INTERFACE
+    FILE_SET HEADERS
+    BASE_DIRS
+      libb
+    FILES
+      libb/libb.h
+)
 
 target_link_libraries(libb PUBLIC liba)
 
-install(TARGETS liba EXPORT liba DESTINATION lib)
+install(TARGETS liba EXPORT liba FILE_SET HEADERS)
 install(PACKAGE_INFO liba DESTINATION cps EXPORT liba)
 
-install(TARGETS libb EXPORT libb DESTINATION lib)
+install(TARGETS libb EXPORT libb FILE_SET HEADERS)
 install(PACKAGE_INFO libb DESTINATION cps EXPORT libb)

+ 1 - 6
Tests/RunCMake/CpsExportImportInstall/app.c

@@ -1,11 +1,6 @@
+#include <libb.h>
 #include <stdio.h>
 
-extern
-#ifdef _WIN32
-__declspec(dllimport)
-#endif
-int ask(void);
-
 int main(void)
 {
   printf("%i\n", ask());

+ 0 - 0
Tests/RunCMake/CpsExportImportInstall/liba.c → Tests/RunCMake/CpsExportImportInstall/liba/liba.c


+ 3 - 0
Tests/RunCMake/CpsExportImportInstall/liba/liba.h

@@ -0,0 +1,3 @@
+#pragma once
+
+int answer(void);

+ 1 - 5
Tests/RunCMake/CpsExportImportInstall/libb.c → Tests/RunCMake/CpsExportImportInstall/libb/libb.c

@@ -1,8 +1,4 @@
-extern
-#ifdef _WIN32
-__declspec(dllimport)
-#endif
-int answer(void);
+#include <liba.h>
 
 #ifdef _WIN32
 __declspec(dllexport)

+ 3 - 0
Tests/RunCMake/CpsExportImportInstall/libb/libb.h

@@ -0,0 +1,3 @@
+#pragma once
+
+int ask(void);

+ 10 - 0
Tests/RunCMake/ExportPackageInfo/FileSetHeaders-check.cmake

@@ -0,0 +1,10 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/FileSetHeaders-build")
+
+file(READ "${out_dir}/foo.cps" content)
+
+string(JSON component GET "${content}" "components" "foo")
+
+expect_array("${component}" 1 "includes")
+expect_value("${component}" "${CMAKE_CURRENT_LIST_DIR}/foo" "includes" 0)

+ 29 - 0
Tests/RunCMake/ExportPackageInfo/FileSetHeaders.cmake

@@ -0,0 +1,29 @@
+add_library(foo INTERFACE)
+
+# Primarily this tests for de-duplication of the BASE_DIRS, ensuring DESTINATION
+# genex have no effect on build exports is a bonus covering a very unlikely bug
+
+target_sources(foo
+  INTERFACE
+    FILE_SET no_genex
+    TYPE HEADERS
+    BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/foo
+
+  INTERFACE
+    FILE_SET genex
+    TYPE HEADERS
+    BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/foo
+)
+
+install(
+  TARGETS foo
+  EXPORT foo
+  DESTINATION .
+
+  FILE_SET no_genex
+    DESTINATION no_genex
+
+  FILE_SET genex
+    DESTINATION $<$<CONFIG:FAKE_CONFIG>:FAKE_DEST>genex
+)
+export(EXPORT foo PACKAGE_INFO foo)

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

@@ -41,3 +41,4 @@ run_cmake(TargetTypes)
 run_cmake(DependsMultiple)
 run_cmake(LinkOnly)
 run_cmake(PerConfigGeneration)
+run_cmake(FileSetHeaders)

+ 28 - 0
Tests/RunCMake/InstallPackageInfo/FileSetHeaders-check.cmake

@@ -0,0 +1,28 @@
+include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
+
+set(out_dir "${RunCMake_BINARY_DIR}/FileSetHeaders-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
+
+file(READ "${out_dir}/foo.cps" content)
+
+string(JSON component GET "${content}" "components" "foo")
+
+expect_array("${component}" 1 "includes")
+expect_value("${component}" "@prefix@/no_genex" "includes" 0)
+
+file(GLOB configs "${out_dir}/foo@*.cps")
+list(LENGTH configs configs_len)
+
+if(NOT configs_len)
+  set(RunCMake_TEST_FAILED
+    "No configuration-specific CPS files were generated" PARENT_SCOPE)
+  return()
+endif()
+
+foreach(config ${configs})
+  file(READ "${config}" content)
+
+  string(JSON component GET "${content}" "components" "foo")
+
+  expect_array("${component}" 1 "includes")
+  expect_value("${component}" "@prefix@/genex" "includes" 0)
+endforeach()

+ 38 - 0
Tests/RunCMake/InstallPackageInfo/FileSetHeaders.cmake

@@ -0,0 +1,38 @@
+add_library(foo INTERFACE)
+
+target_sources(foo
+  INTERFACE
+    FILE_SET no_genex
+    TYPE HEADERS
+
+  INTERFACE
+    FILE_SET no_genex_dup
+    TYPE HEADERS
+
+  INTERFACE
+    FILE_SET genex
+    TYPE HEADERS
+
+  INTERFACE
+    FILE_SET genex_dup
+    TYPE HEADERS
+)
+
+install(
+  TARGETS foo
+  EXPORT foo
+  DESTINATION .
+
+  FILE_SET no_genex
+    DESTINATION no_genex
+
+  FILE_SET no_genex_dup
+    DESTINATION no_genex
+
+  FILE_SET genex
+    DESTINATION $<$<CONFIG:FAKE_CONFIG>:FAKE_DEST>genex
+
+  FILE_SET genex_dup
+    DESTINATION $<$<CONFIG:FAKE_CONFIG>:FAKE_DEST>genex
+)
+install(PACKAGE_INFO foo DESTINATION cps EXPORT foo)

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

@@ -49,4 +49,5 @@ run_cmake(TargetTypes)
 run_cmake(DependsMultiple)
 run_cmake(DependsMultipleNotInstalled)
 run_cmake(PerConfigGeneration)
+run_cmake(FileSetHeaders)
 run_cmake_install(Destination)