Side effects of cmake_minimum_required in dependency provider

Hi all,

in general all top-level CMakeLists.txt, modules and CMake script files should have a cmake_minimum_required statement at the top.

I wonder now if there is a good reason to not apply cmake_minimum_required also to toolchain files, find modules (in config and module mode) and to dependency provider files.

Our find modules do not only contain simple find_program calls and target definitions, but provide high level functions which can be used in project context, like FindDoxygen and doxygen_add_docs.

Also in the toolchain files there is some logic to calculate the correct path to tools and setup compiler flags based on cache variables (which basically provide abstractions for embedded compiler settings not available in plain CMake).

In all those files we use advanced features of the CMake which were subject to policy changes in the past. To make sure that these are working correctly I tend to set cmake_minimum_required everywhere. In addition even simple find_program calls could behave differently depending on policies (CMP0144, CMP109, CMP0134, CMP0074). So it looks like a good idea to establish a certain minimum version / policy.

From an organizational perspective the version requirement is not a problem as we operate in a controlled environment and already require a specific minimum version in all of our projects.

What made me wonder is the fact that in the CMake source repository I couldn’t find a call to cmake_minimum_required in the shipped find modules. Some of the newer find modules have cmake_policy calls.

So it did my own reasearch, see this this repo.

For toolchain files and find_package calls in module and config mode I couldn‘t find any negative impact besides that the variable CMAKE_MINIMUM_REQUIRED_VERSION is overwritten in the project context and does not reflect the actual policy version in place.

Regarding CMAKE_MINIMUM_REQUIRED_VERSION there was already a discussion in another thread which left me with the impression that I should not really bother on the value of this variable.

But for the dependency provider use case the situation is different. When I place a cmake_minimum_required at the top of the file the policy settings „infects“ the project where find_package is called. The issue can be fixed by dropping the cmake_minimum_required call and a using a block(SCOPE_FOR POLICIES) and cmake_policy inside the provider macro, see the annotated source code in the example.

After this research I am now considering to use a cmake_minimum_required in general in all CMake files for the sake of simplicity and a block with cmake_policy for the provider use case.

I did not find any information on this specific topic in the official documentation, in Craig Scott’s (@craig.scott) outstanding book “Professional CMake” there is a hint in the recommendation section of the dependency provider chapter:

Ensure that every file given to CMAKE_PROJECT_TOP_LEVEL_INCLUDES starts with its own cmake_minimum_required call.

In the general use case where one controls the project as well as the dependency provider this should not be a problem. But when the dependency provider file is provided e.g. by some third party package manager the versions could be different.

What are your experiences / recommendations? Is there a technical reason to not put cmake_minimum_required in those files?

Regards
Georg

Hello, I had a bad experience with calling cmake_minimum_required in config files: Environment variable <PackageName>_ROOT ignored with a cmake minimum required 3.22

Hi @tpadioleau,

there was no final answer to this question. But in the meanwhile I tried out several things and came to the conclusion that cmake_minimum_requiredshould be used only for project CMakeLists.txt files and simple scripts. In find modules (be it module or config mode), toolchain files a cmake_policy(VERSION) has no unwanted side effects. In the dependency provider use case the whole file should be inside a cmake_policy block.

Regards
Georg

I meant to respond to this long ago, but it got buried in my inbox.

As you’ve observed, there is a side effect to calling cmake_minimum_required(), that being it updates the CMAKE_MINIMUM_REQUIRED_VERSION variable. Personally, I never trust or use that variable, and your post explains the main reason why.

If all you want to do is enforce policy settings, then cmake_policy() is the more appropriate command, except for at the top of the top level CMakeLists.txt in your project which must call cmake_minimum _required().

I generally try to avoid changing policy settings inside function or macro bodies. Instead, aim to specify your policies outside of the function() / endfunction() and macro() / endmacro() blocks. Those policies are then what get applied to the function and macro bodies and it doesn’t affect the caller of those. There are cases where you can’t avoid having to change policy settings inside a function or macro, but those tend to be in CMake’s own implementations. If you are unlucky enough to need to do it in your own functions or macros, make sure you surround them with a cmake_policy(PUSH) and cmake_policy(POP), or even better with block(SCOPE_FOR POLICIES) and endblock().

For dependency providers specifically, it’s quite a tricky situation. The command you register as the dependency provider command should be a macro, not a function. Whatever you do in that macro affects the calling scope. You absolutely don’t want to call cmake_minimum_required() inside that macro, since it will leak out the CMAKE_MINIMUM_REQUIRED_VERSION variable change. Prefer to adjust your policy settings outside the macro() and endmacro() commands instead, as mentioned above. Note that your original post suggests not doing that and only setting the policy settings inside your macro in a way that doesn’t “infect” the dependency, but this isn’t foolproof. All you’re doing is putting your dependency provider at the mercy of whatever settings were in place at the point where your dependency provider is brought into the build via PROJECT_TOP_LEVEL_INCLUDES. That means the top level project’s policy settings will be controlling the policy behavior of your provider logic. Maybe that’s fine for a tightly controlled environment, but it is not appropriate outside of that.

To expand on the above for the benefit of other reads, the really tricky part is that the policy settings of your provider macro will be used as the initial policy settings of any dependencies your provider brings in via find_package() or FetchContent_MakeAvailable(). Hopefully, the latter doesn’t matter because all projects should be calling cmake_minimum_required() as their first command anyway, so your provider macro’s policy settings should ultimately get overridden inside the dependency. But for find_package() (and find_dependency()), it depends on what the dependency’s packageName-config.cmake or FindPackageName.cmake file does. Unfortunately, it is very common for such files to not specify their policy settings. Even some of CMake’s own Find modules don’t. There’s not a whole lot you can do about that, you’re somewhat at the mercy of the authors of the packages and Find modules you consume. Things like the CMAKE_POLICY_DEFAULT_CMPNNNN variable may end up being you have to resort to if you’re stuck with an older dependency that hasn’t adopted more recent CMake policy behaviors.

Toolchain files are an interesting case. They should also avoid cmake_minimum_required() so they don’t leak changes to the CMAKE_MINIMUM_REQUIRED_VERSION variable. They should generally call cmake_policy() instead, but it is actually relatively uncommon for toolchain files to bother doing this. That doesn’t mean it is good practice to omit it, I’m only highlighting that most of the examples you see probably won’t do it. I’m frequently guilty of the very same thing, mostly because the toolchain files I write tend to be used in highly controlled situations where the policy behaviors are not a factor due to the projects they are used in. The part folks sometimes forget about is that toolchain files are used in more contexts than just the main project. When CMake does its initial compiler checks, and for things like try_compile() calls, CMake creates a separate mini-project with its own sub-build off to the side and uses the toolchain file there. The policy settings used for that mini-project are fully controlled by CMake as internal implementation detail, not by your project.

in Craig Scott’s (@craig.scott) outstanding book “Professional CMake” there is a hint in the recommendation section of the dependency provider chapter:

Ensure that every file given to CMAKE_PROJECT_TOP_LEVEL_INCLUDES starts with its own cmake_minimum_required call.

After writing out the above and going through the implications, I think I need to update that advice. It should be recommending cmake_policy() instead.