Support multiple cmake minimum versions for one project - cmake

I have a fairly popular open source project that has a minimum cmake version set that is fairly old (~Oct 2013).
cmake_minimum_required(VERSION 2.8.12)
This is great for supporting a wide variety of users, but it makes adding new things to our cmake builds challenging, since we cannot use any of the new features in later cmake versions. This leads to complicated cmake files and more maintenance burden to emulate future improvements made to cmake.
I would like to support two CMakeLists.txt files for the project that have different minimum versions, one legacy one at the 2.8.12 version, and another more modern one at a later 3.x version. Is there any canonical way to do this? It doesn't look like there is a way to support multiple CMakeFiles.txt at the root of the project.
The naive way is to just rename the legacy file CMakeFiles_Legacy.txt and have users that depend on the older cmakes manually switch out the CMakeFiles.txt on their own.

tl;dr: Just don't support old versions.
CMake is absurdly easy to upgrade. Literally every halfway-modern development platform natively packages at least CMake 3.16+, but Windows and macOS are both way ahead of the curve with constant updates to the included version.
Visual Studio receives periodic updates and 2022 includes CMake 3.21. The version on Chocolatey is always up to date.
On macOS the Homebrew version is always up to date.
On Debian derivatives, Kitware provides an official APT repo that is always up to date. There is also a Snap package that is always up to date.
On most other Linux distros, one can use the official binaries directly from Kitware without sudo access at all. They have x86_64 and aarch64 binaries. The binaries are statically linked and require only libc6 as a dependency. Glibc has been ABI-stable since 1997. It will work on your system. But even if it doesn't, building a recent CMake version isn't difficult anyway. Your users maintaining such archaic systems shouldn't be your liability.
(aside: I wrote an entire blog post on this... https://alexreinking.com/blog/how-to-use-cmake-without-the-agonizing-pain-part-1.html)

Related

Problem building GNURadio in custom environment

I am trying to build the latest GNURadio package on my development system. Unfortunately this system configuration is tightly controlled and I can't just install new packages of software on it as it is used to develop a product and all development systems are kept in lockstep. We are currently on an older version of RedHat.
While I cannot modify the system includes I can download and use newer versions of packages locally (in non-system directories) as long as that doesn't affect the product build/debug environment. Normally this isn't a problem.
However, when building GNURadio I found that our development platforms use an older version of the Boost libraries than is required to build GNURadio. So, I got the latest version of Boost and extracted it into my local (home) directory. I found several directions for, I thought, instructing CMake to use additional include directories. Unfortunately, this hasn't seemed to work with the Boost libraries. CMake keeps complaining that it finds the older version of Boost and not the newer one I have extracted locally.
I have tried using
-DCMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES=<dir>
and
-DCMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES_BEFORE=<dir>
and this had no effect. I then tried adding the following to the top-level CMakeLists.txt file:
SET(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)
SET(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES <dir>)
or, even
include_directories(BEFORE <dir>)
Again, no joy.
I did a bit of digging and found that there is a GrBoost.cmake module and it had an additional configuration for the boost directory so I added this:
list(PREPEND BOOST_LIBRARYDIR "<dir>")
to the top of the file. Again, no luck.
I've never used CMake before (and I'm not really keen on learning yet another build system if I don't have to - our company just switched to bazel and I am coming up to speed on that) so I am flying blind here.
What do I have to do to get CMake to look in my local directory to find the Boost stuff I downloaded?
Ok. As it often happens, just after asking the question I was able to find an answer.
It turns out that there is a command-line option to CMake (CMAKE_PREFIX_PATH=<dir>) where you can specify additional base paths to search for CMake config files. I just added this to the command-line and it was found just fine.
I wasn't even aware that Boost came with such config files. Live and learn.
#vre's comment would have probably worked just as well (maybe better, in fact).

Is there an easy way to find what the minimum required version for CMake should be?

Is there a convenient way to find what the minimum compatible CMake version should be (besides testing it with every single version)? I'm looking for a tool that will parse my CMakeLists.txt, find all the features I'm using, look up the CMake version they were added, and spit out the maximum. A quick look though cmake --help didn't show an option to do this. Is there an external tool that will do this for me?
As of now, there is no such tool. This is because there are a number of problems with creating such a tool.
Most importantly is that CMake has never made any promises of forwards-compatibility. Listfiles authored with a newer version of CMake have never been guaranteed to work with older versions, regardless what cmake_minimum_required setting appears. This is due to several factors: new features added, improved logic in Find modules and compiler detection, and so on. Basically anything that doesn't break old code, but makes newer code more intelligent and robust, even without source changes.
Thus, a tool that only checked for new features (like generator expressions) would miss out on changes to other parts of the overall system.
This means any such tool would have to model CMake so closely that it would be easier to simply automate running an old CMake version and testing the build. If you feel you need to do this, you should automate it yourself.
Taking a step back, CMake is amazingly, ludicrously easy to upgrade and you can save yourself and others a lot of backwards compatibility headaches like this by simply sidestepping the issue. Use a recent version, declare it as a minimum and encourage your users to upgrade. On Linux, Kitware provides statically linked executables for x86 and arm that require nothing besides libc. I have never heard of these executables not working. I use them on old Raspberry Pis. I have yet to see any remotely valid reason to support versions of CMake older than a year or so.

How can I tell which minimum CMake version to require in CMakeLists.txt?

In CMakeLists.txt scripts, it is customary to have a:
cmake_minimum_required(VERSION x.y)
for the appropriate x.y version number. But - how can you tell what that minimum version is? I don't work with older versions; and as my distribution gets updated, so does CMake, so I may have even introduced commands requiring newer versions over time and I just don't know it.
I could theoretically require the versions I've tested this CMakeLists.txt with - but they're pretty new, and I don't want to restrict users of my code to just that.
There is no automatic way to do this. Here are some approaches I've used:
If you have used CMake for a while, you may roughly remember what features are newer and older. Select some of the features you use which you think are newer, and read the documentation to discover what version supports them.
If you can install just one or two old versions, it may be enough. CMake 2.8.x is very popular, so installing that and testing to see if it works (perhaps fixing it so it does) would be a nice service to some users.
Don't worry about it. Set the minimum to 2.8.10 and then accept patches or bug reports if users have specific issues. It's not likely that setting this number too low will cause any serious harm--typically it will just result in a different error message than "The CMake version is too old."
tl;dr: Try building with different CMake versions.
Download the CMake binary from https://cmake.org/files/ according to the claimed minimal required version. You can unpack them in some directory and use this CMake to ensure it is still the minimal required version. You can have as many versions as you wish in parallel. Don't forget to delete the build directory.
A problem you did not mention, but is also important: Check your code with newer version. New policies can lead to dozens of warnings, many projects try avoiding warnings in releases. So it might be good to have the latest version with the same procedure.

Determine appropriate version for cmake_minimum_required()

From a set of CMakeLists.txt files, how can I determine an appropriate version number for cmake_minimum_required()? Is there a better way than being familiar with the history of CMake features and using trial and error?
CMake has per-version documentation on its website. Using it, you may check that all features you use in your project are supported by a specific CMake version.
Features include command names, command options and their possible values, names of modules shipped with CMake.
Usually project does not need precise minimum CMake version. You may take reasonable version, which is accessible for users, and check whether this version supports all features you use.

Which CMake version as the minimum?

I want to define a minimum version to CMake with "cmake_minimum_required" facility.
I have seen that some project set minimum version 2.8 some others set 3.0 or 3.2.
I would like to learn your opinions and best practices about the topic.
The cmake_minimum_required() function is used to avoid any cryptic error messages due to the CMakeLists.txt assuming a later version of CMake than the one installed on the current host.
As an example, failing early, and with a clear message...
CMake 3.2 or higher is required. You are running version 2.8.12.2
...is to be preferred over something more cryptic (much) later on...
In file included from /home/foouser/src/testprj/string.cpp:1:0:
/home/foouser/src/testprj/string.hpp:94:29: error: ‘std::is_same’ has not been declared
...just because, in this example, the older CMake version does not support set( CMAKE_CXX_STANDARD 11 ). I am sure you'll agree.
The ideal setting would be:
The oldest version with all the features your script needs.
Maximal compatibility with people running older versions, as well as with your script. But it requires testing which version exactly it was that first supported your constructs. So it usually boils down to:
The oldest version you have tested that has all the features your script needs.
That's probably good enough for most projects. And if you are the only one actually working on the project, and testing for CMake compatibility is really low on your list, you will probably end up with:
The version you are currently using.
This latter approach has a serious drawback once somebody else attempts to compile your project. Quite a few people are not using the latest version of everything. On Linux in particular, the default is to use whatever the package manager gives you. Ubuntu wily, for example, is currently at version 3.2.2 -- you may have a later version, but unless you need a later version, you shouldn't require it (as that means people won't be able to build your project without first installing a newer version of CMake, manually).
What you should not be doing is...
Requiring a very old version, but not actually testing against that old version (NO!).
The reasons should be obvious -- building could fail, without the user getting any hint as to why things went wrong.