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

CPS: Support FILE_SET HEADERS

Fixes: #26806
This commit is contained in:
Vito Gamberini
2025-06-25 14:47:02 -04:00
parent 0a385b880a
commit 7db44fbfb8
21 changed files with 315 additions and 40 deletions

View File

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

View File

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

View File

@@ -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 = {});
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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