cmake_minimum_required: Optionally set policies with version range

Teach `cmake_minimum_required` and `cmake_policy(VERSION)` to support a
version range of the form `<min>[...<max>]`.  Define this to mean that
version `<min>` is required, but known policies up to those introduced
by `<max>` will be set to `NEW`.  This will allow projects to easily
specify a range of versions for which they have been updated.
This commit is contained in:
Brad King 2018-03-16 12:42:36 -04:00
parent 6a41aa2abd
commit 45408b5ea1
18 changed files with 210 additions and 41 deletions

View File

@ -4,11 +4,15 @@ cmake_minimum_required
Set the minimum required version of cmake for a project and Set the minimum required version of cmake for a project and
update `Policy Settings`_ to match the version given:: update `Policy Settings`_ to match the version given::
cmake_minimum_required(VERSION major.minor[.patch[.tweak]] cmake_minimum_required(VERSION <min>[...<max>] [FATAL_ERROR])
[FATAL_ERROR])
If the current version of CMake is lower than that required it will ``<min>`` and the optional ``<max>`` are each CMake versions of the form
stop processing the project and report an error. ``major.minor[.patch[.tweak]]``, and the ``...`` is literal.
If the running version of CMake is lower than the ``<min>`` required
version it will stop processing the project and report an error.
The optional ``<max>`` version, if specified, must be at least the
``<min>`` version and affects policy settings as described below.
The ``FATAL_ERROR`` option is accepted but ignored by CMake 2.6 and The ``FATAL_ERROR`` option is accepted but ignored by CMake 2.6 and
higher. It should be specified so CMake versions 2.4 and lower fail higher. It should be specified so CMake versions 2.4 and lower fail
@ -30,21 +34,23 @@ Policy Settings
The ``cmake_minimum_required(VERSION)`` command implicitly invokes the The ``cmake_minimum_required(VERSION)`` command implicitly invokes the
:command:`cmake_policy(VERSION)` command to specify that the current :command:`cmake_policy(VERSION)` command to specify that the current
project code is written for the given version of CMake. project code is written for the given range of CMake versions.
All policies introduced in the specified version or earlier will be All policies known to the running version of CMake and introduced
set to use NEW behavior. All policies introduced after the specified in the ``<min>`` (or ``<max>``, if specified) version or earlier will
version will be unset. This effectively requests behavior preferred be set to use ``NEW`` behavior. All policies introduced in later
versions will be unset. This effectively requests behavior preferred
as of a given CMake version and tells newer CMake versions to warn as of a given CMake version and tells newer CMake versions to warn
about their new policies. about their new policies.
When a version higher than 2.4 is specified the command implicitly When a ``<min>`` version higher than 2.4 is specified the command
implicitly invokes::
cmake_policy(VERSION <min>[...<max>])
which sets CMake policies based on the range of versions specified.
When a ``<min>`` version 2.4 or lower is given the command implicitly
invokes:: invokes::
cmake_policy(VERSION major[.minor[.patch[.tweak]]]) cmake_policy(VERSION 2.4[...<max>])
which sets the cmake policy version level to the version specified.
When version 2.4 or lower is given the command implicitly invokes::
cmake_policy(VERSION 2.4)
which enables compatibility features for CMake 2.4 and lower. which enables compatibility features for CMake 2.4 and lower.

View File

@ -24,17 +24,22 @@ The ``cmake_policy`` command is used to set policies to ``OLD`` or ``NEW``
behavior. While setting policies individually is supported, we behavior. While setting policies individually is supported, we
encourage projects to set policies based on CMake versions:: encourage projects to set policies based on CMake versions::
cmake_policy(VERSION major.minor[.patch[.tweak]]) cmake_policy(VERSION <min>[...<max>])
Specify that the current CMake code is written for the given ``<min>`` and the optional ``<max>`` are each CMake versions of the form
version of CMake. All policies introduced in the specified version or ``major.minor[.patch[.tweak]]``, and the ``...`` is literal. The ``<min>``
earlier will be set to use ``NEW`` behavior. All policies introduced version must be at least ``2.4`` and at most the running version of CMake.
after the specified version will be unset (unless the The ``<max>`` version, if specified, must be at least the ``<min>`` version
but may exceed the running version of CMake.
This specifies that the current CMake code is written for the given
range of CMake versions. All policies known to the running version of CMake
and introduced in the ``<min>`` (or ``<max>``, if specified) version
or earlier will be set to use ``NEW`` behavior. All policies
introduced in later versions will be unset (unless the
:variable:`CMAKE_POLICY_DEFAULT_CMP<NNNN>` variable sets a default). :variable:`CMAKE_POLICY_DEFAULT_CMP<NNNN>` variable sets a default).
This effectively requests behavior preferred as of a given CMake This effectively requests behavior preferred as of a given CMake
version and tells newer CMake versions to warn about their new policies. version and tells newer CMake versions to warn about their new policies.
The policy version specified must be at least 2.4 or the command will
report an error.
Note that the :command:`cmake_minimum_required(VERSION)` Note that the :command:`cmake_minimum_required(VERSION)`
command implicitly calls ``cmake_policy(VERSION)`` too. command implicitly calls ``cmake_policy(VERSION)`` too.

View File

@ -0,0 +1,8 @@
policy-version-range
--------------------
* The :command:`cmake_minimum_required` and :command:`cmake_policy(VERSION)`
commands now accept a version range using the form ``<min>[...<max>]``.
The ``<min>`` version is required but policies are set based on the
``<max>`` version. This allows projects to specify a range of versions
for which they have been updated and avoid explicit policy settings.

View File

@ -1,7 +1,5 @@
CMAKE_MINIMUM_REQUIRED_VERSION CMAKE_MINIMUM_REQUIRED_VERSION
------------------------------ ------------------------------
Version specified to :command:`cmake_minimum_required` command The ``<min>`` version of CMake given to the most recent call to the
:command:`cmake_minimum_required(VERSION)` command.
Variable containing the ``VERSION`` component specified in the
:command:`cmake_minimum_required` command.

View File

@ -1002,7 +1002,8 @@ int cmCPackGenerator::DoPackage()
{ // scope that enables package generators to run internal scripts with { // scope that enables package generators to run internal scripts with
// latest CMake policies enabled // latest CMake policies enabled
cmMakefile::ScopePushPop pp{ this->MakefileMap }; cmMakefile::ScopePushPop pp{ this->MakefileMap };
this->MakefileMap->SetPolicyVersion(cmVersion::GetCMakeVersion()); this->MakefileMap->SetPolicyVersion(cmVersion::GetCMakeVersion(),
std::string());
if (!this->PackageFiles() || cmSystemTools::GetErrorOccuredFlag()) { if (!this->PackageFiles() || cmSystemTools::GetErrorOccuredFlag()) {
cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem compressing the directory" cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem compressing the directory"

View File

@ -45,9 +45,24 @@ bool cmCMakeMinimumRequired::InitialPass(std::vector<std::string> const& args,
return this->EnforceUnknownArguments(); return this->EnforceUnknownArguments();
} }
// Separate the <min> version and any trailing ...<max> component.
std::string::size_type const dd = version_string.find("...");
std::string const version_min = version_string.substr(0, dd);
std::string const version_max = dd != std::string::npos
? version_string.substr(dd + 3, std::string::npos)
: std::string();
if (dd != std::string::npos &&
(version_min.empty() || version_max.empty())) {
std::ostringstream e;
e << "VERSION \"" << version_string
<< "\" does not have a version on both sides of \"...\".";
this->SetError(e.str());
return false;
}
// Save the required version string. // Save the required version string.
this->Makefile->AddDefinition("CMAKE_MINIMUM_REQUIRED_VERSION", this->Makefile->AddDefinition("CMAKE_MINIMUM_REQUIRED_VERSION",
version_string.c_str()); version_min.c_str());
// Get the current version number. // Get the current version number.
unsigned int current_major = cmVersion::GetMajorVersion(); unsigned int current_major = cmVersion::GetMajorVersion();
@ -61,10 +76,10 @@ bool cmCMakeMinimumRequired::InitialPass(std::vector<std::string> const& args,
unsigned int required_minor = 0; unsigned int required_minor = 0;
unsigned int required_patch = 0; unsigned int required_patch = 0;
unsigned int required_tweak = 0; unsigned int required_tweak = 0;
if (sscanf(version_string.c_str(), "%u.%u.%u.%u", &required_major, if (sscanf(version_min.c_str(), "%u.%u.%u.%u", &required_major,
&required_minor, &required_patch, &required_tweak) < 2) { &required_minor, &required_patch, &required_tweak) < 2) {
std::ostringstream e; std::ostringstream e;
e << "could not parse VERSION \"" << version_string << "\"."; e << "could not parse VERSION \"" << version_min << "\".";
this->SetError(e.str()); this->SetError(e.str());
return false; return false;
} }
@ -78,7 +93,7 @@ bool cmCMakeMinimumRequired::InitialPass(std::vector<std::string> const& args,
current_patch == required_patch && current_tweak < required_tweak)) { current_patch == required_patch && current_tweak < required_tweak)) {
// The current version is too low. // The current version is too low.
std::ostringstream e; std::ostringstream e;
e << "CMake " << version_string e << "CMake " << version_min
<< " or higher is required. You are running version " << " or higher is required. You are running version "
<< cmVersion::GetCMakeVersion(); << cmVersion::GetCMakeVersion();
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str()); this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
@ -95,9 +110,9 @@ bool cmCMakeMinimumRequired::InitialPass(std::vector<std::string> const& args,
this->Makefile->IssueMessage( this->Makefile->IssueMessage(
cmake::AUTHOR_WARNING, cmake::AUTHOR_WARNING,
"Compatibility with CMake < 2.4 is not supported by CMake >= 3.0."); "Compatibility with CMake < 2.4 is not supported by CMake >= 3.0.");
this->Makefile->SetPolicyVersion("2.4"); this->Makefile->SetPolicyVersion("2.4", version_max);
} else { } else {
this->Makefile->SetPolicyVersion(version_string.c_str()); this->Makefile->SetPolicyVersion(version_min, version_max);
} }
return true; return true;

View File

@ -156,6 +156,23 @@ bool cmCMakePolicyCommand::HandleVersionMode(
this->SetError("VERSION given too many arguments"); this->SetError("VERSION given too many arguments");
return false; return false;
} }
this->Makefile->SetPolicyVersion(args[1].c_str()); std::string const& version_string = args[1];
// Separate the <min> version and any trailing ...<max> component.
std::string::size_type const dd = version_string.find("...");
std::string const version_min = version_string.substr(0, dd);
std::string const version_max = dd != std::string::npos
? version_string.substr(dd + 3, std::string::npos)
: std::string();
if (dd != std::string::npos &&
(version_min.empty() || version_max.empty())) {
std::ostringstream e;
e << "VERSION \"" << version_string
<< "\" does not have a version on both sides of \"...\".";
this->SetError(e.str());
return false;
}
this->Makefile->SetPolicyVersion(version_min, version_max);
return true; return true;
} }

View File

@ -1464,7 +1464,7 @@ void cmMakefile::Configure()
this->SetCheckCMP0000(true); this->SetCheckCMP0000(true);
// Implicitly set the version for the user. // Implicitly set the version for the user.
this->SetPolicyVersion("2.4"); this->SetPolicyVersion("2.4", std::string());
} }
} }
bool hasProject = false; bool hasProject = false;
@ -4149,9 +4149,10 @@ void cmMakefile::PopSnapshot(bool reportError)
assert(this->StateSnapshot.IsValid()); assert(this->StateSnapshot.IsValid());
} }
bool cmMakefile::SetPolicyVersion(std::string const& version_min) bool cmMakefile::SetPolicyVersion(std::string const& version_min,
std::string const& version_max)
{ {
return cmPolicies::ApplyPolicyVersion(this, version_min); return cmPolicies::ApplyPolicyVersion(this, version_min, version_max);
} }
bool cmMakefile::HasCMP0054AlreadyBeenReported( bool cmMakefile::HasCMP0054AlreadyBeenReported(

View File

@ -285,7 +285,8 @@ public:
bool SetPolicy(cmPolicies::PolicyID id, cmPolicies::PolicyStatus status); bool SetPolicy(cmPolicies::PolicyID id, cmPolicies::PolicyStatus status);
bool SetPolicy(const char* id, cmPolicies::PolicyStatus status); bool SetPolicy(const char* id, cmPolicies::PolicyStatus status);
cmPolicies::PolicyStatus GetPolicyStatus(cmPolicies::PolicyID id) const; cmPolicies::PolicyStatus GetPolicyStatus(cmPolicies::PolicyID id) const;
bool SetPolicyVersion(std::string const& version_min); bool SetPolicyVersion(std::string const& version_min,
std::string const& version_max);
void RecordPolicies(cmPolicies::PolicyMap& pm); void RecordPolicies(cmPolicies::PolicyMap& pm);
//@} //@}

View File

@ -154,7 +154,8 @@ static bool GetPolicyDefault(cmMakefile* mf, std::string const& policy,
} }
bool cmPolicies::ApplyPolicyVersion(cmMakefile* mf, bool cmPolicies::ApplyPolicyVersion(cmMakefile* mf,
std::string const& version_min) std::string const& version_min,
std::string const& version_max)
{ {
// Parse components of the minimum version. // Parse components of the minimum version.
unsigned int minMajor = 2; unsigned int minMajor = 2;
@ -205,6 +206,42 @@ bool cmPolicies::ApplyPolicyVersion(cmMakefile* mf,
unsigned int polMajor = minMajor; unsigned int polMajor = minMajor;
unsigned int polMinor = minMinor; unsigned int polMinor = minMinor;
unsigned int polPatch = minPatch; unsigned int polPatch = minPatch;
if (!version_max.empty()) {
// Parse components of the maximum version.
unsigned int maxMajor = 0;
unsigned int maxMinor = 0;
unsigned int maxPatch = 0;
unsigned int maxTweak = 0;
if (sscanf(version_max.c_str(), "%u.%u.%u.%u", &maxMajor, &maxMinor,
&maxPatch, &maxTweak) < 2) {
std::ostringstream e;
e << "Invalid policy max version value \"" << version_max << "\". "
<< "A numeric major.minor[.patch[.tweak]] must be given.";
mf->IssueMessage(cmake::FATAL_ERROR, e.str());
return false;
}
// It is an error if the min version is greater than the max version.
if (minMajor > maxMajor || (minMajor == maxMajor && minMinor > maxMinor) ||
(minMajor == maxMajor && minMinor == maxMinor &&
minPatch > maxPatch) ||
(minMajor == maxMajor && minMinor == maxMinor &&
minPatch == maxPatch && minTweak > maxTweak)) {
std::ostringstream e;
e << "Policy VERSION range \"" << version_min << "..." << version_max
<< "\""
<< " specifies a larger minimum than maximum.";
mf->IssueMessage(cmake::FATAL_ERROR, e.str());
return false;
}
// Use the max version as the policy version.
polMajor = maxMajor;
polMinor = maxMinor;
polPatch = maxPatch;
}
return cmPolicies::ApplyPolicyVersion(mf, polMajor, polMinor, polPatch); return cmPolicies::ApplyPolicyVersion(mf, polMajor, polMinor, polPatch);
} }

View File

@ -289,7 +289,8 @@ public:
///! Set a policy level for this listfile ///! Set a policy level for this listfile
static bool ApplyPolicyVersion(cmMakefile* mf, static bool ApplyPolicyVersion(cmMakefile* mf,
std::string const& version_min); std::string const& version_min,
std::string const& version_max);
static bool ApplyPolicyVersion(cmMakefile* mf, unsigned int majorVer, static bool ApplyPolicyVersion(cmMakefile* mf, unsigned int majorVer,
unsigned int minorVer, unsigned int patchVer); unsigned int minorVer, unsigned int patchVer);

View File

@ -612,7 +612,7 @@ bool cmQtAutoGenerator::Run(std::string const& infoFile,
auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot); auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot);
// The OLD/WARN behavior for policy CMP0053 caused a speed regression. // The OLD/WARN behavior for policy CMP0053 caused a speed regression.
// https://gitlab.kitware.com/cmake/cmake/issues/17570 // https://gitlab.kitware.com/cmake/cmake/issues/17570
makefile->SetPolicyVersion("3.9"); makefile->SetPolicyVersion("3.9", std::string());
gg.SetCurrentMakefile(makefile.get()); gg.SetCurrentMakefile(makefile.get());
success = this->Init(makefile.get()); success = this->Init(makefile.get());
} }

View File

@ -0,0 +1,4 @@
^CMAKE_MINIMUM_REQUIRED_VERSION='3\.10'
CMP0071='NEW'
CMP0072='NEW'
CMP0073=''$

View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.10...3.11)
message("CMAKE_MINIMUM_REQUIRED_VERSION='${CMAKE_MINIMUM_REQUIRED_VERSION}'")
foreach(policy CMP0071 CMP0072 CMP0073)
cmake_policy(GET ${policy} status)
message("${policy}='${status}'")
endforeach()

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,56 @@
^CMake Error at RangeBad.cmake:1 \(cmake_minimum_required\):
cmake_minimum_required VERSION "3.11..." does not have a version on both
sides of "...".
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:2 \(cmake_minimum_required\):
cmake_minimum_required VERSION "...3.11" does not have a version on both
sides of "...".
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:3 \(cmake_minimum_required\):
cmake_minimum_required VERSION "..." does not have a version on both sides
of "...".
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:4 \(cmake_minimum_required\):
Invalid policy max version value "4". A numeric
major.minor\[.patch\[.tweak\]\] must be given.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:5 \(cmake_minimum_required\):
Policy VERSION range "3.11...3.10" specifies a larger minimum than maximum.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:6 \(cmake_policy\):
cmake_policy VERSION "3.11..." does not have a version on both sides of
"...".
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:7 \(cmake_policy\):
cmake_policy VERSION "...3.11" does not have a version on both sides of
"...".
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:8 \(cmake_policy\):
cmake_policy VERSION "..." does not have a version on both sides of "...".
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:9 \(cmake_policy\):
Invalid policy max version value "4". A numeric
major.minor\[.patch\[.tweak\]\] must be given.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
+
CMake Error at RangeBad.cmake:10 \(cmake_policy\):
Policy VERSION range "3.11...3.10" specifies a larger minimum than maximum.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)$

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.11...)
cmake_minimum_required(VERSION ...3.11)
cmake_minimum_required(VERSION ...)
cmake_minimum_required(VERSION 3.11...4)
cmake_minimum_required(VERSION 3.11...3.10)
cmake_policy(VERSION 3.11...)
cmake_policy(VERSION ...3.11)
cmake_policy(VERSION ...)
cmake_policy(VERSION 3.11...4)
cmake_policy(VERSION 3.11...3.10)

View File

@ -3,3 +3,5 @@ include(RunCMake)
run_cmake(Before24) run_cmake(Before24)
run_cmake(CompatBefore24) run_cmake(CompatBefore24)
run_cmake(PolicyBefore24) run_cmake(PolicyBefore24)
run_cmake(Range)
run_cmake(RangeBad)