CPS: Get metadata from project

Modify {export,install}(PACKAGE_INFO) commands to inherit version
information from the current project in situations where it seems
reasonable to do so. Add an option to explicitly request inheritance
from a specific project.

This leverages the recently added project(COMPAT_VERSION).
This commit is contained in:
Matthew Woehlke
2025-05-29 15:41:32 -04:00
parent 9adf93d112
commit ecf81c1bc1
26 changed files with 298 additions and 4 deletions

View File

@@ -133,6 +133,7 @@ Exporting Targets to the |CPS|
.. code-block:: cmake
export(EXPORT <export-name> PACKAGE_INFO <package-name>
[PROJECT <project-name>|NO_PROJECT_METADATA]
[APPENDIX <appendix-name>]
[LOWER_CASE_FILE]
[VERSION <version>

View File

@@ -992,6 +992,7 @@ Signatures
.. code-block:: cmake
install(PACKAGE_INFO <package-name> EXPORT <export-name>
[PROJECT <project-name>|NO_PROJECT_METADATA]
[APPENDIX <appendix-name>]
[DESTINATION <dir>]
[LOWER_CASE_FILE]
@@ -1056,6 +1057,14 @@ Signatures
configurations exists. If not specified, CMake will fall back to the
package's available configurations in an unspecified order.
By default, if the specified ``<package-name>`` matches the current CMake
:variable:`PROJECT_NAME`, package metadata will be inherited from the
project. The ``PROJECT <project-name>`` option may be used to specify a
different project from which to inherit metadata. If ``NO_PROJECT_METADATA``
is specified, automatic inheritance of package metadata will be disabled.
In any case, any metadata values specified in the ``install`` command will
take precedence.
If ``APPENDIX`` is specified, rather than generating a top level package
specification, the specified targets will be exported as an appendix to the
named package. Appendices may be used to separate less commonly used targets
@@ -1065,7 +1074,7 @@ Signatures
artifacts produced by multiple build trees.
Appendices are not permitted to change basic package metadata; therefore,
none of ``VERSION``, ``COMPAT_VERSION``, ``VERSION_SCHEMA``,
none of ``PROJECT``, ``VERSION``, ``COMPAT_VERSION``, ``VERSION_SCHEMA``,
``DEFAULT_TARGETS`` or ``DEFAULT_CONFIGURATIONS`` may be specified in
combination with ``APPENDIX``. Additionally, it is strongly recommended that
use of ``LOWER_CASE_FILE`` should be consistent between the main package and

View File

@@ -114,7 +114,7 @@ bool cmExportCommand(std::vector<std::string> const& args,
}
std::vector<std::string> unknownArgs;
Arguments const arguments = parser.Parse(args, &unknownArgs);
Arguments arguments = parser.Parse(args, &unknownArgs);
if (!unknownArgs.empty()) {
status.SetError("Unknown argument: \"" + unknownArgs.front() + "\".");
@@ -219,7 +219,8 @@ bool cmExportCommand(std::vector<std::string> const& args,
status.SetError("PACKAGE_INFO and NAMESPACE are mutually exclusive.");
return false;
}
if (!arguments.Check(status)) {
if (!arguments.Check(status) ||
!arguments.SetMetadataFromProject(status)) {
return false;
}
}

View File

@@ -2202,7 +2202,7 @@ bool HandlePackageInfoMode(std::vector<std::string> const& args,
return false;
}
if (!arguments.Check(status)) {
if (!arguments.Check(status) || !arguments.SetMetadataFromProject(status)) {
return false;
}

View File

@@ -2,10 +2,15 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmPackageInfoArguments.h"
#include <utility>
#include "cmExecutionStatus.h"
#include "cmGeneratorExpression.h"
#include "cmMakefile.h"
#include "cmStateSnapshot.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmValue.h"
template void cmPackageInfoArguments::Bind<void>(cmArgumentParser<void>&,
cmPackageInfoArguments*);
@@ -35,6 +40,14 @@ bool cmPackageInfoArguments::Check(cmExecutionStatus& status,
status.SetError("DEFAULT_CONFIGURATIONS requires PACKAGE_INFO.");
return false;
}
if (!this->ProjectName.empty()) {
status.SetError("PROJECT requires PACKAGE_INFO.");
return false;
}
if (this->NoProjectDefaults) {
status.SetError("NO_PROJECT_METADATA requires PACKAGE_INFO.");
return false;
}
}
// Check for incompatible options.
@@ -53,6 +66,17 @@ bool cmPackageInfoArguments::Check(cmExecutionStatus& status,
"are mutually exclusive.");
return false;
}
if (!this->ProjectName.empty()) {
status.SetError("APPENDIX and PROJECT are mutually exclusive.");
return false;
}
}
if (this->NoProjectDefaults) {
if (!this->ProjectName.empty()) {
status.SetError("PROJECT and NO_PROJECT_METADATA "
"are mutually exclusive.");
return false;
}
}
// Check for options that require other options.
@@ -81,6 +105,66 @@ bool cmPackageInfoArguments::Check(cmExecutionStatus& status,
return true;
}
bool cmPackageInfoArguments::SetMetadataFromProject(cmExecutionStatus& status)
{
// Determine what project to use for inherited metadata.
if (!this->SetEffectiveProject(status)) {
return false;
}
if (this->ProjectName.empty()) {
// We are not inheriting from a project.
return true;
}
cmMakefile& mf = status.GetMakefile();
if (this->Version.empty()) {
cmValue const& version =
mf.GetDefinition(cmStrCat(this->ProjectName, "_VERSION"_s));
if (version) {
this->Version = version;
cmValue const& compatVersion =
mf.GetDefinition(cmStrCat(this->ProjectName, "_COMPAT_VERSION"_s));
if (compatVersion) {
this->VersionCompat = compatVersion;
}
}
}
return true;
}
bool cmPackageInfoArguments::SetEffectiveProject(cmExecutionStatus& status)
{
if (!this->Appendix.empty()) {
// Appendices are not allowed to specify package metadata.
return true;
}
if (this->NoProjectDefaults) {
// User requested that metadata not be inherited.
return true;
}
cmMakefile& mf = status.GetMakefile();
if (!this->ProjectName.empty()) {
// User specified a project; make sure it exists.
if (!mf.GetStateSnapshot().CheckProjectName(this->ProjectName)) {
status.SetError(cmStrCat(R"(PROJECT given invalid project name ")"_s,
this->ProjectName, R"(".)"_s));
return false;
}
} else {
// No project was specified; check if the package name is also a project.
std::string project = mf.GetStateSnapshot().GetProjectName();
if (this->PackageName == project) {
this->ProjectName = std::move(project);
}
}
return true;
}
std::string cmPackageInfoArguments::GetNamespace() const
{
return cmStrCat(this->PackageName, "::"_s);

View File

@@ -47,6 +47,10 @@ public:
/// \c false, forbid specifying any options whatsoever.
bool Check(cmExecutionStatus& status, bool enable = true) const;
/// Set metadata (not already specified) from either the specified project,
/// or from the project which matches the package name.
bool SetMetadataFromProject(cmExecutionStatus& status);
ArgumentParser::NonEmpty<std::string> PackageName;
ArgumentParser::NonEmpty<std::string> Appendix;
ArgumentParser::NonEmpty<std::string> Version;
@@ -56,7 +60,12 @@ public:
ArgumentParser::NonEmpty<std::vector<std::string>> DefaultConfigs;
bool LowerCase = false;
ArgumentParser::NonEmpty<std::string> ProjectName;
bool NoProjectDefaults = false;
private:
bool SetEffectiveProject(cmExecutionStatus& status);
template <typename T>
static void Bind(cmArgumentParser<T>& parser, cmPackageInfoArguments* self)
{
@@ -73,6 +82,10 @@ private:
&cmPackageInfoArguments::DefaultTargets);
Bind(self, parser, "DEFAULT_CONFIGURATIONS"_s,
&cmPackageInfoArguments::DefaultConfigs);
Bind(self, parser, "PROJECT"_s, &cmPackageInfoArguments::ProjectName);
Bind(self, parser, "NO_PROJECT_METADATA"_s,
&cmPackageInfoArguments::NoProjectDefaults);
}
template <typename T, typename U,

View File

@@ -8,3 +8,15 @@ CMake Error at BadArgs1\.cmake:4 \(export\):
export VERSION_SCHEMA requires VERSION\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs1\.cmake:5 \(export\):
export PROJECT and NO_PROJECT_METADATA are mutually exclusive\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs1\.cmake:6 \(export\):
export PROJECT given invalid project name "bar"\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -2,3 +2,5 @@ add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
export(EXPORT foo PACKAGE_INFO foo COMPAT_VERSION 1.0)
export(EXPORT foo PACKAGE_INFO foo VERSION_SCHEMA simple)
export(EXPORT foo PACKAGE_INFO foo PROJECT foo NO_PROJECT_METADATA)
export(EXPORT foo PACKAGE_INFO foo PROJECT bar)

View File

@@ -14,3 +14,9 @@ CMake Error at BadArgs2\.cmake:5 \(export\):
export APPENDIX and DEFAULT_CONFIGURATIONS are mutually exclusive\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs2\.cmake:6 \(export\):
export APPENDIX and PROJECT are mutually exclusive\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -3,3 +3,4 @@ install(TARGETS foo EXPORT foo DESTINATION .)
export(EXPORT foo PACKAGE_INFO foo APPENDIX test VERSION 1.0)
export(EXPORT foo PACKAGE_INFO foo APPENDIX test DEFAULT_TARGETS foo)
export(EXPORT foo PACKAGE_INFO foo APPENDIX test DEFAULT_CONFIGURATIONS Release)
export(EXPORT foo PACKAGE_INFO foo APPENDIX test PROJECT foo)

View File

@@ -26,3 +26,15 @@ CMake Error at BadArgs4\.cmake:7 \(export\):
export DEFAULT_CONFIGURATIONS requires PACKAGE_INFO\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs4\.cmake:8 \(export\):
export PROJECT requires PACKAGE_INFO\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs4\.cmake:9 \(export\):
export NO_PROJECT_METADATA requires PACKAGE_INFO\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -5,3 +5,5 @@ export(EXPORT foo APPENDIX test)
export(EXPORT foo VERSION 1.0)
export(EXPORT foo DEFAULT_TARGETS foo)
export(EXPORT foo DEFAULT_CONFIGURATIONS Release)
export(EXPORT foo PROJECT foo)
export(EXPORT foo NO_PROJECT_METADATA)

View File

@@ -0,0 +1,8 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/NoProjectMetadata-build")
file(READ "${out_dir}/foo.cps" content)
expect_value("${content}" "foo" "name")
expect_missing("${content}" "version")
expect_missing("${content}" "compat_version")

View File

@@ -0,0 +1,10 @@
project(foo VERSION 1.2.3 COMPAT_VERSION 1.1.0)
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
export(
EXPORT foo
PACKAGE_INFO foo
NO_PROJECT_METADATA
)

View File

@@ -0,0 +1,18 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/ProjectMetadata-build")
file(READ "${out_dir}/foo.cps" content)
expect_value("${content}" "foo" "name")
expect_value("${content}" "1.2.3" "version")
expect_value("${content}" "1.1.0" "compat_version")
file(READ "${out_dir}/test1.cps" content)
expect_value("${content}" "test1" "name")
expect_value("${content}" "1.2.3" "version")
expect_value("${content}" "1.1.0" "compat_version")
file(READ "${out_dir}/test2.cps" content)
expect_value("${content}" "test2" "name")
expect_value("${content}" "1.4.7" "version")
expect_missing("${content}" "compat_version")

View File

@@ -0,0 +1,25 @@
project(foo VERSION 1.2.3 COMPAT_VERSION 1.1.0)
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
# Test inheriting from project matching package name.
export(
EXPORT foo
PACKAGE_INFO foo
)
# Test inheriting from a specified project.
export(
EXPORT foo
PROJECT foo
PACKAGE_INFO test1
)
# Test that inheriting doesn't override explicitly specified metadata.
export(
EXPORT foo
PROJECT foo
PACKAGE_INFO test2
VERSION 1.4.7
)

View File

@@ -31,6 +31,8 @@ run_cmake(LinkOnlyRecursive)
run_cmake(Appendix)
run_cmake(InterfaceProperties)
run_cmake(Metadata)
run_cmake(ProjectMetadata)
run_cmake(NoProjectMetadata)
run_cmake(Minimal)
run_cmake(MinimalVersion)
run_cmake(LowerCaseFile)

View File

@@ -8,3 +8,15 @@ CMake Error at BadArgs1.cmake:4 \(install\):
install VERSION_SCHEMA requires VERSION.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs1\.cmake:5 \(install\):
install PROJECT and NO_PROJECT_METADATA are mutually exclusive\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs1\.cmake:6 \(install\):
install PROJECT given invalid project name "bar"\.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -2,3 +2,5 @@ add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
install(PACKAGE_INFO test EXPORT foo COMPAT_VERSION 1.0)
install(PACKAGE_INFO test EXPORT foo VERSION_SCHEMA simple)
install(PACKAGE_INFO test EXPORT foo PROJECT foo NO_PROJECT_METADATA)
install(PACKAGE_INFO test EXPORT foo PROJECT bar)

View File

@@ -14,3 +14,9 @@ CMake Error at BadArgs2.cmake:5 \(install\):
install APPENDIX and DEFAULT_CONFIGURATIONS are mutually exclusive.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)
CMake Error at BadArgs2.cmake:6 \(install\):
install APPENDIX and PROJECT are mutually exclusive.
Call Stack \(most recent call first\):
CMakeLists\.txt:3 \(include\)

View File

@@ -3,3 +3,4 @@ install(TARGETS foo EXPORT foo DESTINATION .)
install(PACKAGE_INFO test EXPORT foo APPENDIX test VERSION 1.0)
install(PACKAGE_INFO test EXPORT foo APPENDIX test DEFAULT_TARGETS foo)
install(PACKAGE_INFO test EXPORT foo APPENDIX test DEFAULT_CONFIGURATIONS test)
install(PACKAGE_INFO test EXPORT foo APPENDIX test PROJECT foo)

View File

@@ -0,0 +1,8 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/NoProjectMetadata-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
file(READ "${out_dir}/foo.cps" content)
expect_value("${content}" "foo" "name")
expect_missing("${content}" "version")
expect_missing("${content}" "compat_version")

View File

@@ -0,0 +1,11 @@
project(foo VERSION 1.2.3 COMPAT_VERSION 1.1.0)
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
install(
PACKAGE_INFO foo
DESTINATION cps
EXPORT foo
NO_PROJECT_METADATA
)

View File

@@ -0,0 +1,18 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/ProjectMetadata-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983")
file(READ "${out_dir}/foo.cps" content)
expect_value("${content}" "foo" "name")
expect_value("${content}" "1.2.3" "version")
expect_value("${content}" "1.1.0" "compat_version")
file(READ "${out_dir}/test1.cps" content)
expect_value("${content}" "test1" "name")
expect_value("${content}" "1.2.3" "version")
expect_value("${content}" "1.1.0" "compat_version")
file(READ "${out_dir}/test2.cps" content)
expect_value("${content}" "test2" "name")
expect_value("${content}" "1.4.7" "version")
expect_missing("${content}" "compat_version")

View File

@@ -0,0 +1,28 @@
project(foo VERSION 1.2.3 COMPAT_VERSION 1.1.0)
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
# Test inheriting from project matching package name.
install(
PACKAGE_INFO foo
DESTINATION cps
EXPORT foo
)
# Test inheriting from a specified project.
install(
PACKAGE_INFO test1
DESTINATION cps
EXPORT foo
PROJECT foo
)
# Test that inheriting doesn't override explicitly specified metadata.
install(
PACKAGE_INFO test2
DESTINATION cps
EXPORT foo
PROJECT foo
VERSION 1.4.7
)

View File

@@ -39,6 +39,8 @@ run_cmake(DependsMultipleDifferentSets)
run_cmake(Appendix)
run_cmake(InterfaceProperties)
run_cmake(Metadata)
run_cmake(ProjectMetadata)
run_cmake(NoProjectMetadata)
run_cmake(Minimal)
run_cmake(MinimalVersion)
run_cmake(LowerCaseFile)