tl;dr Is there any way to determine the existence of a CMake feature if it does not have a policy?
I am trying to extend my CMake project with (optional) support for the new FILE_SET feature introduced with CMake 3.23.
I thought I could just check for CMAKE_VERSION to determine if the FILE_SET feature is available and then use it. And in general it works, as CMAKE_VERSION gives me back the version of the cmake executable:
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.23")
target_sources( my_target PUBLIC FILE_SET ... )
target_sources( my_target PUBLIC ... )
However, I noticed even if I am using cmake_minimum_required( VERSION 3.20...3.22 ), which pretty much says that my code is not supposed to know and therefore not use the FILE_SET feature, the value of CMAKE_VERSION still shows the real version of the cmake executable and therefore the first branch from the above if would be chosen. (Assume above code snippet comes from some 3rd-party CMake module into my CMake code.)
As my implementation was not entirely done - I was still using install( TARGETS ... ) without installing the FILE_SET - I thought I could prevent choosing this half-implemented execution path by using the above if.
However, it obviously does not work this way and CMake gives me an error “install TARGETS target my_target is exported but not all of its file sets are installed”.
So, is it somehow possibly to honor the chosen CMake (compatibility) version and not use a feature that comes from a later CMake version (even if using an up-to-date cmake executable)?
(Maybe I am just blind and overlooked the correct CMake variable but I was unable to find it.)
No, the only way to make sure is to actually use and test the minimum CMake version.
The version check is the way to do this.
Eh. It more says that “this project requires 3.20 to work at all, but it knows about 3.22 and is OK with policies added to that point”. Conditionals to do other things if better replacements are available are still possible.
Think of it this way: if CMake locked its features behind minimum CMake declarations, then CMake’s own modules would “never” be able to use new features. Instead, they use the new features (and set the policies to NEW where needed for such things for itself).
So, for cmake_minumum_required( VERSION min...max ) there is no way to determine max (especially if CMAKE_VERSION is newer than max)!?
That is pretty sad. Because it prevents the following use-case (I was trying to explain above):
Assume that from some 3rd-party CMake module you get some functionality which conditionally uses a newer CMake feature if the used cmake version is new enough, e.g. it uses target_sources( my_target ... FILE_SET ... ) if the cmake version is at least 3.23.
If I then use that 3rd-party CMake module in my CMake code and there I just do a simple install( TARGETS my_target ... ) ignoring (or not knowing about) the special FILE_SET feature (especially because it uses cmake_minimum_required( VERSION 3.20...3.22 )) the code will work as long as my users use a cmake executable which is not newer than 3.22.x. However, as soon as the cmake executable is updated to 3.23 or newer, the code will suddenly break (with “install TARGETS target my_target is exported but not all of its file sets are installed”).
Further, this behavior is grist to the mill of people that want to stick with ancient CMake versions.
I was always advising people (also in my CMake conference talks) to update to more recent CMake versions because CMake is great with backwards-compatibility (due to cmake_minimum_required) and they thereby get problems fixed (and because it is simple/cheap to update CMake).
However, I might have to reconsider this advice, as problems as the one described above could arise.
I really think this is a viable problem which could easily be fixed if one could check what the maximal chosen CMake version is.
There is no “maximal chosen CMake version” though. There is “newest version the code is aware of”, but that is not a cap on what CMake is allowed because CMake continues working with older code as much as is possible.
I would expect it to have some documentation to this effect. Ideally, there would be some way to tell it “please don’t use the new behavior”. If the API wrapped the source listing and installation, it could do this, but since it appears that it does not, FILE_SET will likely need to be an opt-in mechanism on the caller side.
CMake has great backwards compatibility (old code works with new CMake releases). What it does not guarantee is forward compatibility (new code is benign in old CMake releases). The recommendation is usually to use the minimum CMake version’s documentation when developing (or pay attention to the “available since” stanzas now available). The ...<max> is just to say “I have adapted for the policy changes up to this version” not "I have started using all the new features available as of ...<max>"; that needs some other mechanism.
As an example, a project may say ...3.17, but this does not mean that it guarantees working with the Ninja Multi-Config generator that was added in that version.