CPS: Validate package version

Add version validation to cmExportPackageInfoGenerator. Specifically, if
a package uses the "simple" version schema (which is the default if no
schema is specified), validate that the specified version actually
conforms to the schema and issue an error if it does not. Also, issue a
warning if the schema is not recognized.
This commit is contained in:
Matthew Woehlke
2025-11-26 15:51:56 -05:00
parent fb66a14da2
commit cc508826b4
18 changed files with 210 additions and 3 deletions

View File

@@ -32,7 +32,7 @@ bool cmExportBuildPackageInfoGenerator::GenerateMainFile(std::ostream& os)
return false;
}
if (!this->CheckDefaultTargets()) {
if (!this->CheckPackage()) {
return false;
}

View File

@@ -66,7 +66,7 @@ bool cmExportInstallPackageInfoGenerator::GenerateMainFile(std::ostream& os)
}
}
if (!this->CheckDefaultTargets()) {
if (!this->CheckPackage()) {
return false;
}

View File

@@ -16,6 +16,8 @@
#include <cm3p/json/value.h>
#include <cm3p/json/writer.h>
#include "cmsys/RegularExpression.hxx"
#include "cmArgumentParserTypes.h"
#include "cmExportSet.h"
#include "cmFindPackageStack.h"
@@ -88,6 +90,63 @@ void BuildArray(Json::Value& object, std::string const& property,
}
}
}
bool CheckSimpleVersion(std::string const& version)
{
cmsys::RegularExpression regex("^[0-9]+([.][0-9]+)*([-+].*)?$");
return regex.find(version);
}
}
bool cmExportPackageInfoGenerator::CheckVersion() const
{
if (!this->PackageVersion.empty()) {
std::string const& schema = [&] {
if (this->PackageVersionSchema.empty()) {
return std::string{ "simple" };
}
return cmSystemTools::LowerCase(this->PackageVersionSchema);
}();
bool (*validator)(std::string const&) = nullptr;
bool result = true;
if (schema == "simple"_s) {
validator = &CheckSimpleVersion;
} else if (schema == "dpkg"_s || schema == "rpm"_s ||
schema == "pep440"_s) {
// TODO
// We don't validate these at this time. Eventually, we would like to do
// so, but will probably need to introduce a policy whether to treat
// invalid versions as an error.
} else if (schema != "custom"_s) {
this->IssueMessage(MessageType::AUTHOR_WARNING,
cmStrCat("Package \""_s, this->GetPackageName(),
"\" uses unrecognized version schema \""_s,
this->PackageVersionSchema, "\"."_s));
}
if (validator) {
if (!(*validator)(this->PackageVersion)) {
this->ReportError(cmStrCat("Package \""_s, this->GetPackageName(),
"\" version \""_s, this->PackageVersion,
"\" does not conform to the \""_s, schema,
"\" schema."_s));
result = false;
}
if (!this->PackageVersionCompat.empty() &&
!(*validator)(this->PackageVersionCompat)) {
this->ReportError(
cmStrCat("Package \""_s, this->GetPackageName(),
"\" compatibility version \""_s, this->PackageVersionCompat,
"\" does not conform to the \""_s, schema, "\" schema."_s));
result = false;
}
}
return result;
}
return true;
}
bool cmExportPackageInfoGenerator::CheckDefaultTargets() const

View File

@@ -47,7 +47,10 @@ protected:
// Methods to implement export file code generation.
bool GenerateImportFile(std::ostream& os) override;
bool CheckDefaultTargets() const;
bool CheckPackage() const
{
return this->CheckVersion() && this->CheckDefaultTargets();
}
Json::Value GeneratePackageInfo() const;
Json::Value* GenerateImportTarget(Json::Value& components,
@@ -82,6 +85,9 @@ protected:
cmGeneratorTarget const* linkedTarget) override;
private:
bool CheckVersion() const;
bool CheckDefaultTargets() const;
void GenerateInterfaceLinkProperties(
bool& result, Json::Value& component, cmGeneratorTarget const* target,
ImportPropertyMap const& properties) const;

View File

@@ -4,6 +4,10 @@ include(RunCMake)
run_cmake(ExperimentalGate)
run_cmake(ExperimentalWarning)
# Test version check author warning
# TODO Move to be with other tests when experimental gate is removed.
run_cmake(VersionCheckWarning)
# Enable experimental feature and suppress warnings
set(RunCMake_TEST_OPTIONS
-Wno-dev
@@ -48,3 +52,6 @@ run_cmake(FileSetHeaders)
run_cmake(DependencyVersionCMake)
run_cmake(DependencyVersionCps)
run_cmake(TransitiveSymbolicComponent)
run_cmake(VersionCheck)
# run_cmake(VersionCheckWarning)
run_cmake(VersionCheckError)

View File

@@ -0,0 +1,38 @@
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
# Try exporting a 'properly' simple version.
export(EXPORT foo PACKAGE_INFO foo1 VERSION 1.2.3)
# Try exporting a version with many components.
export(EXPORT foo PACKAGE_INFO foo2 VERSION 1.21.23.33.37.42.9.0.12)
# Try exporting a version with a label.
export(EXPORT foo PACKAGE_INFO foo3 VERSION "1.2.3+git1234abcd")
# Try exporting a version with a different label.
export(EXPORT foo PACKAGE_INFO foo4 VERSION "1.2.3-0.example")
# Try exporting with the schema explicitly specified.
export(
EXPORT foo
PACKAGE_INFO foo5
VERSION "1.2.3-0.example"
VERSION_SCHEMA "simple"
)
# Try exporting with a custom-schema version.
export(
EXPORT foo
PACKAGE_INFO foo6
VERSION "foo!test"
VERSION_SCHEMA "custom"
)
# Try exporting with a recognized but not-checked schema.
export(
EXPORT foo
PACKAGE_INFO foo7
VERSION "invalid"
VERSION_SCHEMA "pep440"
)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
CMake Error in CMakeLists\.txt:
Package "foo" version "1.2.3rc1" does not conform to the "simple" schema\.

View File

@@ -0,0 +1,5 @@
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
# Try exporting a non-conforming version.
export(EXPORT foo PACKAGE_INFO foo VERSION "1.2.3rc1")

View File

@@ -0,0 +1,3 @@
CMake Warning \(dev\) in CMakeLists\.txt:
Package "foo" uses unrecognized version schema "unrecognized"\.
This warning is for project developers\. Use -Wno-dev to suppress it\.

View File

@@ -0,0 +1,15 @@
set(
CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO
"b80be207-778e-46ba-8080-b23bba22639e"
)
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
# Try exporting with an unrecognized schema.
export(
EXPORT foo
PACKAGE_INFO foo
VERSION "irrelevant"
VERSION_SCHEMA "unrecognized"
)

View File

@@ -4,6 +4,10 @@ include(RunCMake)
run_cmake(ExperimentalGate)
run_cmake(ExperimentalWarning)
# Test version check author warning
# TODO Move to be with other tests when experimental gate is removed.
run_cmake(VersionCheckWarning)
# Enable experimental feature and suppress warnings
set(RunCMake_TEST_OPTIONS
-Wno-dev
@@ -57,4 +61,7 @@ run_cmake(DependencyVersionCMake)
run_cmake(DependencyVersionCps)
run_cmake(TransitiveSymbolicComponent)
run_cmake(InstallSymbolicComponent)
run_cmake(VersionCheck)
# run_cmake(VersionCheckWarning)
run_cmake(VersionCheckError)
run_cmake_install(Destination)

View File

@@ -0,0 +1,38 @@
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
# Try exporting a 'properly' simple version.
install(PACKAGE_INFO foo1 EXPORT foo VERSION 1.2.3)
# Try exporting a version with many components.
install(PACKAGE_INFO foo2 EXPORT foo VERSION 1.21.23.33.37.42.9.0.12)
# Try exporting a version with a label.
install(PACKAGE_INFO foo3 EXPORT foo VERSION "1.2.3+git1234abcd")
# Try exporting a version with a different label.
install(PACKAGE_INFO foo4 EXPORT foo VERSION "1.2.3-0.example")
# Try exporting with the schema explicitly specified.
install(
PACKAGE_INFO foo5
EXPORT foo
VERSION "1.2.3-0.example"
VERSION_SCHEMA "simple"
)
# Try exporting with a custom-schema version.
install(
PACKAGE_INFO foo6
EXPORT foo
VERSION "foo!test"
VERSION_SCHEMA "custom"
)
# Try exporting with a recognized but not-checked schema.
install(
PACKAGE_INFO foo7
EXPORT foo
VERSION "invalid"
VERSION_SCHEMA "pep440"
)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,2 @@
CMake Error in CMakeLists\.txt:
Package "foo" version "1.2.3rc1" does not conform to the "simple" schema\.

View File

@@ -0,0 +1,5 @@
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
# Try exporting a non-conforming version.
install(PACKAGE_INFO foo EXPORT foo VERSION "1.2.3rc1")

View File

@@ -0,0 +1,3 @@
CMake Warning \(dev\) in CMakeLists\.txt:
Package "foo" uses unrecognized version schema "unrecognized"\.
This warning is for project developers\. Use -Wno-dev to suppress it\.

View File

@@ -0,0 +1,15 @@
set(
CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO
"b80be207-778e-46ba-8080-b23bba22639e"
)
add_library(foo INTERFACE)
install(TARGETS foo EXPORT foo DESTINATION .)
# Try exporting with an unrecognized schema.
install(
PACKAGE_INFO foo
EXPORT foo
VERSION "irrelevant"
VERSION_SCHEMA "unrecognized"
)