export: Import/export target property for license

Add import/export support and documentation for the SPDX_LICENSE
target property. A target's license can be specified by setting
this property to an SPDX license expression. CMake and CPS-format
export files generated with `export()` or `install()` will
retain the license information. CMake also imports the license
property for imported targets.

Closes: #26706
This commit is contained in:
Daniel Tierney
2025-05-12 13:55:45 -04:00
parent 4802077fb9
commit df3e246bc1
19 changed files with 121 additions and 14 deletions

View File

@@ -328,6 +328,7 @@ Properties on Targets
/prop_tgt/LIBRARY_OUTPUT_DIRECTORY_CONFIG
/prop_tgt/LIBRARY_OUTPUT_NAME
/prop_tgt/LIBRARY_OUTPUT_NAME_CONFIG
/prop_tgt/SPDX_LICENSE
/prop_tgt/LINK_DEPENDS
/prop_tgt/LINK_DEPENDS_NO_SHARED
/prop_tgt/LINK_DIRECTORIES

View File

@@ -0,0 +1,14 @@
SPDX_LICENSE
------------
.. versionadded:: 4.1
Specify the license of a target using a |SPDX|_ (SPDX) `License Expression`_.
See the SPDX `License List`_ for a list of commonly used licenses and their
identifiers.
.. _SPDX: https://spdx.dev/
.. |SPDX| replace:: System Package Data Exchange
.. _License Expression: https://spdx.github.io/spdx-spec/v3.0.1/annexes/spdx-license-expressions/
.. _License List: https://spdx.org/licenses/

View File

@@ -117,6 +117,9 @@ bool cmExportFileGenerator::PopulateInterfaceProperties(
this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE",
target, properties);
this->PopulateInterfaceProperty("SPDX_LICENSE", target, preprocessRule,
properties);
std::string errorMessage;
if (!this->PopulateCxxModuleExportProperties(
target, properties, preprocessRule, includesDestinationDirs,

View File

@@ -205,7 +205,10 @@ bool cmExportPackageInfoGenerator::GenerateInterfaceProperties(
this->GenerateInterfaceListProperty(result, component, target, "includes",
"INCLUDE_DIRECTORIES"_s, properties);
// TODO: description, license
this->GenerateProperty(result, component, target, "license", "SPDX_LICENSE",
properties);
// TODO: description
return result;
}
@@ -464,6 +467,24 @@ void cmExportPackageInfoGenerator::GenerateInterfaceListProperty(
}
}
void cmExportPackageInfoGenerator::GenerateProperty(
bool& result, Json::Value& component, cmGeneratorTarget const* target,
std::string const& outName, std::string const& inName,
ImportPropertyMap const& properties) const
{
auto const& iter = properties.find(inName);
if (iter == properties.end()) {
return;
}
if (!ForbidGeneratorExpressions(target, inName, iter->second)) {
result = false;
return;
}
component[outName] = iter->second;
}
Json::Value cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties(
std::string const& suffix, ImportPropertyMap const& properties) const
{

View File

@@ -101,6 +101,11 @@ private:
std::string const& outName, cm::string_view inName,
ImportPropertyMap const& properties) const;
void GenerateProperty(bool& result, Json::Value& component,
cmGeneratorTarget const* target,
std::string const& outName, std::string const& inName,
ImportPropertyMap const& properties) const;
std::string const PackageName;
std::string const PackageVersion;
std::string const PackageVersionCompat;

View File

@@ -564,10 +564,10 @@ std::string cmPackageInfoReader::ResolvePath(std::string path) const
return path;
}
void cmPackageInfoReader::SetOptionalProperty(cmTarget* target,
cm::string_view property,
cm::string_view configuration,
Json::Value const& value) const
void cmPackageInfoReader::SetImportProperty(cmTarget* target,
cm::string_view property,
cm::string_view configuration,
Json::Value const& value) const
{
if (!value.isNull()) {
std::string fullprop;
@@ -582,6 +582,15 @@ void cmPackageInfoReader::SetOptionalProperty(cmTarget* target,
}
}
void cmPackageInfoReader::SetMetaProperty(cmTarget* target,
cm::string_view property,
Json::Value const& value) const
{
if (!value.isNull()) {
target->SetProperty(property.data(), value.asString());
}
}
void cmPackageInfoReader::SetTargetProperties(
cmMakefile* makefile, cmTarget* target, Json::Value const& data,
std::string const& package, cm::string_view configuration) const
@@ -630,14 +639,14 @@ void cmPackageInfoReader::SetTargetProperties(
});
// Add link name/location(s).
this->SetOptionalProperty(target, "LOCATION"_s, configuration,
data["location"]);
this->SetImportProperty(target, "LOCATION"_s, configuration,
data["location"]);
this->SetOptionalProperty(target, "IMPLIB"_s, configuration,
data["link_location"]);
this->SetImportProperty(target, "IMPLIB"_s, configuration,
data["link_location"]);
this->SetOptionalProperty(target, "SONAME"_s, configuration,
data["link_name"]);
this->SetImportProperty(target, "SONAME"_s, configuration,
data["link_name"]);
// Add link languages.
for (std::string const& originalLang : ReadList(data, "link_languages")) {
@@ -659,6 +668,11 @@ void cmPackageInfoReader::SetTargetProperties(
cmStrCat("$<LINK_ONLY:"_s, NormalizeTargetName(dep, package), '>');
AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib);
}
// Add other information.
if (configuration.empty()) {
this->SetMetaProperty(target, "SPDX_LICENSE"_s, data["license"]);
}
}
cmTarget* cmPackageInfoReader::AddLibraryComponent(

View File

@@ -94,9 +94,11 @@ private:
void SetTargetProperties(cmMakefile* makefile, cmTarget* target,
Json::Value const& data, std::string const& package,
cm::string_view configuration) const;
void SetOptionalProperty(cmTarget* target, cm::string_view property,
cm::string_view configuration,
Json::Value const& value) const;
void SetImportProperty(cmTarget* target, cm::string_view property,
cm::string_view configuration,
Json::Value const& value) const;
void SetMetaProperty(cmTarget* target, cm::string_view property,
Json::Value const& value) const;
std::string ResolvePath(std::string path) const;

View File

@@ -22,6 +22,7 @@ function(run_ExportImport_test case)
endfunction()
run_ExportImport_test(SharedDep)
run_ExportImport_test(SpdxLicenseProperty)
function(run_ExportImportBuildInstall_test case)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-export-build)

View File

@@ -0,0 +1,6 @@
add_library(foo INTERFACE)
set_property(TARGET foo PROPERTY SPDX_LICENSE "BSD-3-Clause")
install(TARGETS foo EXPORT foo)
install(EXPORT foo DESTINATION lib/cmake/foo)
install(FILES foo-config.cmake.in RENAME foo-config.cmake DESTINATION lib/cmake/foo)

View File

@@ -0,0 +1,6 @@
find_package(foo REQUIRED CONFIG NO_DEFAULT_PATH)
get_property(SPDX_LICENSE TARGET foo PROPERTY SPDX_LICENSE)
if(NOT SPDX_LICENSE STREQUAL "BSD-3-Clause")
message(FATAL_ERROR
"Expected SPDX_LICENSE property to be 'BSD-3-Clause' but got '${SPDX_LICENSE}'")
endif()

View File

@@ -22,3 +22,4 @@ expect_array("${component}" 1 "link_flags")
expect_value("${component}" "--needed" "link_flags" 0)
expect_array("${component}" 1 "link_libraries")
expect_value("${component}" "/usr/lib/libm.so" "link_libraries" 0)
expect_value("${component}" "BSD-3-Clause" "license")

View File

@@ -10,6 +10,7 @@ target_include_directories(
target_link_directories(foo INTERFACE /opt/foo/lib)
target_link_options(foo INTERFACE --needed)
target_link_libraries(foo INTERFACE /usr/lib/libm.so)
set_property(TARGET foo PROPERTY SPDX_LICENSE "BSD-3-Clause")
install(TARGETS foo EXPORT foo DESTINATION .)
export(EXPORT foo PACKAGE_INFO foo)

View File

@@ -16,3 +16,4 @@ expect_missing("${content}" "components" "foo" "link_features")
expect_missing("${content}" "components" "foo" "link_flags")
expect_missing("${content}" "components" "foo" "link_libraries")
expect_missing("${content}" "components" "foo" "requires")
expect_missing("${content}" "components" "foo" "license")

View File

@@ -22,3 +22,4 @@ expect_array("${component}" 1 "link_flags")
expect_value("${component}" "--needed" "link_flags" 0)
expect_array("${component}" 1 "link_libraries")
expect_value("${component}" "/usr/lib/libm.so" "link_libraries" 0)
expect_value("${component}" "BSD-3-Clause" "license")

View File

@@ -10,6 +10,7 @@ target_include_directories(
target_link_directories(foo INTERFACE /opt/foo/lib)
target_link_options(foo INTERFACE --needed)
target_link_libraries(foo INTERFACE /usr/lib/libm.so)
set_property(TARGET foo PROPERTY SPDX_LICENSE "BSD-3-Clause")
install(TARGETS foo EXPORT foo DESTINATION .)
install(PACKAGE_INFO foo DESTINATION cps EXPORT foo)

View File

@@ -16,3 +16,4 @@ expect_missing("${content}" "components" "foo" "link_features")
expect_missing("${content}" "components" "foo" "link_flags")
expect_missing("${content}" "components" "foo" "link_libraries")
expect_missing("${content}" "components" "foo" "requires")
expect_missing("${content}" "components" "foo" "license")

View File

@@ -17,6 +17,9 @@ run_cmake(VersionLimit2)
run_cmake(TransitiveVersion)
run_cmake(CustomVersion)
# Metadata Tests
run_cmake(SupplementalAttributes)
# Version-matching failure tests
run_cmake(MissingVersion1)
run_cmake(MissingVersion2)

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 4.0)
include(Setup.cmake)
set(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
find_package(SupplementalAttributesTest REQUIRED COMPONENTS Sample)
get_target_property(license SupplementalAttributesTest::Sample "SPDX_LICENSE")
if (NOT "${license}" STREQUAL "BSD-3-Clause")
message(SEND_ERROR "SupplementalAttributesTest wrong license ${license} !")
endif()

View File

@@ -0,0 +1,12 @@
{
"cps_version": "0.13",
"name": "SupplementalAttributesTest",
"version": "1.0",
"cps_path": "@prefix@/cps",
"components": {
"Sample": {
"type": "interface",
"license": "BSD-3-Clause"
}
}
}