CPS: Implement exporting of file sets

Add (partial) file set export to CPS. For now, only headers ("includes")
are supported, as that is the only overlap with the specification.
(Source file sets are not supported by CPS at this time, and the hope is
that C++ modules will never be supported, as that information should
already be available via the `cpp_module_metadata` information.) CPS
also supports "embeds", but CMake doesn't (yet).

This also introduces our first official use of CPS extensions, used to
record the CMake file set name.
This commit is contained in:
Matthew Woehlke
2026-06-30 15:08:02 -04:00
parent 8d229ec702
commit a5b38fb898
16 changed files with 222 additions and 0 deletions
@@ -14,7 +14,11 @@
#include "cmAlgorithms.h"
#include "cmDiagnosticContext.h"
#include "cmDiagnostics.h"
#include "cmGenExContext.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorFileSet.h"
#include "cmGeneratorTarget.h"
#include "cmList.h"
#include "cmPackageInfoArguments.h"
#include "cmStateTypes.h"
@@ -84,6 +88,7 @@ bool cmExportBuildPackageInfoGenerator::GenerateMainFile(std::ostream& os)
// Set configuration-agnostic properties for component.
this->GenerateInterfaceProperties(*component, target, properties);
this->GenerateTargetFileSets(*component, target);
}
this->GeneratePackageRequires(root);
@@ -125,6 +130,35 @@ void cmExportBuildPackageInfoGenerator::GenerateInterfacePropertiesConfig(
}
}
void cmExportBuildPackageInfoGenerator::GenerateTargetFileSets(
Json::Value& fileSets, cmGeneratorTarget const* target,
cmGeneratorFileSet const* fileSet, cmTargetExport const* /*targetExport*/,
std::string const& type) const
{
cm::GenEx::Context context{ target->LocalGenerator, {} };
std::map<std::string, std::vector<std::string>> files;
auto eval = [&files](std::string&& baseDir, std::string&& relPath,
std::string&& /*file*/) {
files[std::move(baseDir)].emplace_back(std::move(relPath));
};
if (fileSet->EvaluateFiles(context, target, eval)) {
this->IssueDiagnostic(
cmDiagnostics::CMD_AUTHOR,
cmStrCat("The \""_s, target->GetName(),
"\" target's interface file set \""_s, fileSet->GetName(),
"\" of type \""_s, fileSet->GetType(),
"\" contains context-sensitive information, which is not "
"supported. The file set will not be exported."_s));
return;
}
for (auto const& i : files) {
this->GenerateTargetFileSet(fileSets, fileSet, type, i.first, i.second);
}
}
std::string cmExportBuildPackageInfoGenerator::GetCxxModulesDirectory() const
{
return this->CxxModulesDirectory;
@@ -48,4 +48,11 @@ protected:
std::string const& config);
std::string GetCxxModulesDirectory() const override;
using cmExportPackageInfoGenerator::GenerateTargetFileSets;
void GenerateTargetFileSets(Json::Value& fileSets,
cmGeneratorTarget const* target,
cmGeneratorFileSet const* fileSet,
cmTargetExport const* targetExport,
std::string const& type) const override;
};
@@ -17,8 +17,10 @@
#include <cm3p/json/value.h>
#include "cmAlgorithms.h"
#include "cmDiagnostics.h"
#include "cmExportSet.h"
#include "cmFileSetMetadata.h"
#include "cmGenExContext.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorFileSet.h"
#include "cmGeneratorTarget.h"
@@ -115,6 +117,7 @@ bool cmExportInstallPackageInfoGenerator::GenerateMainFile(std::ostream& os)
if (!this->GenerateFileSetProperties(*component, gt, te, packagePath)) {
return false;
}
this->GenerateTargetFileSets(*component, gt, te);
}
this->GeneratePackageRequires(root);
@@ -289,3 +292,43 @@ bool cmExportInstallPackageInfoGenerator::GenerateFileSetProperties(
}
return true;
}
void cmExportInstallPackageInfoGenerator::GenerateTargetFileSets(
Json::Value& fileSets, cmGeneratorTarget const* target,
cmGeneratorFileSet const* fileSet, cmTargetExport const* targetExport,
std::string const& type) const
{
cm::optional<std::string> dest =
this->GetFileSetDirectory(target, targetExport, fileSet);
if (!dest) {
this->IssueDiagnostic(
cmDiagnostics::CMD_AUTHOR,
cmStrCat("The \""_s, target->GetName(),
"\" target's interface file set \""_s, fileSet->GetName(),
"\" of type \""_s, fileSet->GetType(),
"\" has a context-sensitive destination, which is not "
"supported. The file set will not be exported."_s));
return;
}
cm::GenEx::Context context{ target->LocalGenerator, {} };
std::vector<std::string> files;
auto eval = [&files](std::string&& /*baseDir*/, std::string&& relPath,
std::string&& /*file*/) {
files.emplace_back(std::move(relPath));
};
if (fileSet->EvaluateFiles(context, target, eval)) {
this->IssueDiagnostic(
cmDiagnostics::CMD_AUTHOR,
cmStrCat("The \""_s, target->GetName(),
"\" target's interface file set \""_s, fileSet->GetName(),
"\" of type \""_s, fileSet->GetType(),
"\" contains context-sensitive information, which is not "
"supported. The file set will not be exported."_s));
return;
}
this->GenerateTargetFileSet(fileSets, fileSet, type, *dest, files);
}
@@ -80,4 +80,11 @@ protected:
cmTargetExport const* te,
std::string const& packagePath,
cm::optional<std::string> config = {});
using cmExportPackageInfoGenerator::GenerateTargetFileSets;
void GenerateTargetFileSets(Json::Value& fileSets,
cmGeneratorTarget const* target,
cmGeneratorFileSet const* fileSet,
cmTargetExport const* targetExport,
std::string const& type) const override;
};
+50
View File
@@ -22,6 +22,8 @@
#include "cmExportSet.h"
#include "cmFindPackageStack.h"
#include "cmGeneratorExpression.h"
#include "cmGeneratorFileSet.h"
#include "cmGeneratorFileSets.h"
#include "cmGeneratorTarget.h"
#include "cmList.h"
#include "cmMakefile.h"
@@ -296,6 +298,54 @@ bool cmExportPackageInfoGenerator::GenerateInterfaceProperties(
return result;
}
void cmExportPackageInfoGenerator::GenerateTargetFileSets(
Json::Value& component, cmGeneratorTarget const* target,
cmTargetExport const* targetExport) const
{
cmGeneratorFileSets const* gfs = target->GetGeneratorFileSets();
for (auto const& type : gfs->GetInterfaceFileSetTypes()) {
// Do we support this file set type?
std::string fsType;
if (type == "HEADERS") {
fsType = "includes";
} else {
continue;
}
// Generate CPS file set(s) for each CMake file set
Json::Value fileSets;
for (cmGeneratorFileSet const* fileSet : gfs->GetInterfaceFileSets(type)) {
this->GenerateTargetFileSets(fileSets, target, fileSet, targetExport,
fsType);
}
if (!fileSets.empty()) {
component["file_sets"] = fileSets;
}
}
}
void cmExportPackageInfoGenerator::GenerateTargetFileSet(
Json::Value& fileSets, cmGeneratorFileSet const* fileSet,
std::string const& type, std::string const& root,
std::vector<std::string> const& files)
{
Json::Value fileSetOut;
fileSetOut["type"] = type;
fileSetOut["root"] = root;
Json::Value filesOut;
for (auto const& f : files) {
filesOut.append(f);
}
fileSetOut["files"] = filesOut;
fileSetOut["extensions"]["cmake"]["name@v1"] = fileSet->GetName();
fileSets.append(fileSetOut);
}
bool cmExportPackageInfoGenerator::NoteLinkedTarget(
cmGeneratorTarget const* target, std::string const& linkedName,
cmGeneratorTarget const* linkedTarget)
+16
View File
@@ -19,8 +19,10 @@ namespace Json {
class Value;
}
class cmGeneratorFileSet;
class cmGeneratorTarget;
class cmPackageInfoArguments;
class cmTargetExport;
/** \class cmExportPackageInfoGenerator
* \brief Generate Common Package Specification package information files
@@ -66,6 +68,20 @@ protected:
Json::Value GenerateInterfaceConfigProperties(
std::string const& suffix, ImportPropertyMap const& properties) const;
void GenerateTargetFileSets(
Json::Value& component, cmGeneratorTarget const* target,
cmTargetExport const* targetExport = nullptr) const;
virtual void GenerateTargetFileSets(Json::Value& fileSets,
cmGeneratorTarget const* target,
cmGeneratorFileSet const* fileSet,
cmTargetExport const* targetExport,
std::string const& type) const = 0;
static void GenerateTargetFileSet(Json::Value& fileSets,
cmGeneratorFileSet const* fileSet,
std::string const& type,
std::string const& root,
std::vector<std::string> const& files);
cm::string_view GetImportPrefixWithSlash() const override;
std::string GetCxxModuleFile(std::string const& /*name*/) const override
@@ -8,3 +8,22 @@ string(JSON component GET "${content}" "components" "foo")
expect_array("${component}" 1 "includes")
expect_value("${component}" "${CMAKE_CURRENT_LIST_DIR}/foo" "includes" 0)
expect_array("${component}" 2 "file_sets")
expect_value("${component}" "includes" "file_sets" 0 "type")
expect_value("${component}" "${CMAKE_CURRENT_LIST_DIR}/foo"
"file_sets" 0 "root")
expect_array("${component}" 2 "file_sets" 0 "files")
expect_value("${component}" "header1.h" "file_sets" 0 "files" 0)
expect_value("${component}" "header2.h" "file_sets" 0 "files" 1)
expect_value("${component}" "no_genex"
"file_sets" 0 "extensions" "cmake" "name@v1")
expect_value("${component}" "includes" "file_sets" 1 "type")
expect_value("${component}" "${CMAKE_CURRENT_LIST_DIR}/foo"
"file_sets" 1 "root")
expect_array("${component}" 1 "file_sets" 1 "files")
expect_value("${component}" "header3.h" "file_sets" 1 "files" 0)
expect_value("${component}" "genex"
"file_sets" 1 "extensions" "cmake" "name@v1")
@@ -8,11 +8,16 @@ target_sources(foo
FILE_SET no_genex
TYPE HEADERS
BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/foo
FILES
${CMAKE_CURRENT_LIST_DIR}/foo/header1.h
${CMAKE_CURRENT_LIST_DIR}/foo/header2.h
INTERFACE
FILE_SET genex
TYPE HEADERS
BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/foo
FILES
${CMAKE_CURRENT_LIST_DIR}/foo/header3.h
)
install(
@@ -9,6 +9,24 @@ string(JSON component GET "${content}" "components" "foo")
expect_array("${component}" 1 "includes")
expect_value("${component}" "@prefix@/no_genex" "includes" 0)
expect_array("${component}" 2 "file_sets")
expect_value("${component}" "includes" "file_sets" 0 "type")
expect_value("${component}" "@prefix@/no_genex" "file_sets" 0 "root")
expect_array("${component}" 2 "file_sets" 0 "files")
expect_value("${component}" "header1.h" "file_sets" 0 "files" 0)
expect_value("${component}" "header2.h" "file_sets" 0 "files" 1)
expect_value("${component}" "no_genex"
"file_sets" 0 "extensions" "cmake" "name@v1")
expect_value("${component}" "includes" "file_sets" 1 "type")
expect_value("${component}" "@prefix@/no_genex" "file_sets" 1 "root")
expect_array("${component}" 2 "file_sets" 1 "files")
expect_value("${component}" "header1.h" "file_sets" 1 "files" 0)
expect_value("${component}" "header2.h" "file_sets" 1 "files" 1)
expect_value("${component}" "no_genex_dup"
"file_sets" 1 "extensions" "cmake" "name@v1")
file(GLOB configs "${out_dir}/foo@*.cps")
list(LENGTH configs configs_len)
@@ -0,0 +1,15 @@
CMake Warning \(author\) at FileSetHeaders\.cmake:[0-9]+ \(install\):
The "foo" target's interface file set "genex" of type "HEADERS" has a
context-sensitive destination, which is not supported\. The file set will
not be exported\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
This warning is for project developers\. Use -Wno-author to suppress it\.
CMake Warning \(author\) at FileSetHeaders\.cmake:[0-9]+ \(install\):
The "foo" target's interface file set "genex_dup" of type "HEADERS" has a
context-sensitive destination, which is not supported\. The file set will
not be exported\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
This warning is for project developers\. Use -Wno-author to suppress it\.
@@ -4,10 +4,18 @@ target_sources(foo
INTERFACE
FILE_SET no_genex
TYPE HEADERS
BASE_DIRS foo
FILES
foo/header1.h
foo/header2.h
INTERFACE
FILE_SET no_genex_dup
TYPE HEADERS
BASE_DIRS foo
FILES
foo/header1.h
foo/header2.h
INTERFACE
FILE_SET genex