`include_guard` useful in files that only define functions?

I quite often have CMake files that only define several CMake functions (and nothing in the surrounding file scope) and I include these in order to be able to call such functions.

I was wondering if an include_guard has any benefit for such a file?


For a little bit of context: I made some observations (which are probably explained somewhere but I do not know where):

  • CMake functions are registered globally and are visible/callable everywhere, regardless where they have been defined, as long as they have been defined before the point of use.
  • Such a function can be defined in a file, and after it gets included this function can be used everywhere (even in parent CMakeLists.txt files).
  • Including such a file multiple times works fine, and the function can still be used everywhere.
  • Later including another file which defines the same named function but with different body overrides the original version of that function.
  • Everywhere, only the latest version of a function is visible/callable.

If I now add an include_guard(GLOBAL) to a file which defines a function that file will only be parsed exactly once, even if included multiple time. This still seems to work fine (as long as no other function with the same name will be defined later).

But does this have any benefit?
Is processing that file faster and will it speed up “cmakeing”?

Good find, I always thought that functions go out of scope like variables.
The desired/implemented behavior itself doesn’t seem to be specified, at least I couldn’t find it in the docs.

Did you know that the previous version of the function is available with an underscore prefix?
So using the previous definition and having a second function inclusion leads to infinite recursion.

# --- main file ---
add_subdirectory(x x1)
# _func doesn't exist yet
add_subdirectory(x x2)
func()
add_subdirectory(x x3)
func()

# --- subdir x ---
function(func)
  message(STATUS "Func")
  _func()
endfunction()

So if someone ever uses that feature to call the overloaded function, there needs to be even an include_guard(GLOBAL)

In fact, without the GLOBAL include guard the function seems to be parsed again and again replacing the previous version.

That is undocumented behavior and actively discouraged. Just don’t do it! Obligatory link to article on the topic:

1 Like

I didn’t mention that CMake commands can also be overriden :wink:

But the scope of functions is also undocumented (at least I couldn’t find it).
That means I cannot provide a function that relies on non-cache variables (e.g. defined in module), because the function may be still used when the variables are already out of scope.

@craig.scott wrote:

You could always add something like the following to your file and see if the include_guard() makes a difference or not:

execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 3)

Good point.
I tried it out and include_guard() makes a difference but only include_guard(GLOBAL) makes a real difference independent of the CMake hierarchy.

However, if I am only defining functions and not doing any sleeps in these files I do not see any measurable speedup when using include_guard(). (At least with my test-setup.)

So, maybe I can ignore this and just not use any include_guard() to be on the safe side and always use the functions I intended to use?..
Or I always use include_guard(GLOBAL) and assume that nobody is ever re-defining my functions?

Maybe CMake should be changed (with a policy for backwards-compatibility) to introduce visibility-scopes for functions?
@craig.scott, maybe this can work just fine together with your proposed changes regarding namespaces?

My earlier post about adding a sleep was nonsense, so I deleted it. :wink: Adding a sleep in code that won’t execute sure isn’t going to make much difference.

I recall doing some performance work a year ago related to FetchContent and ExternalProject. The latter is a relatively bit file and repeatedly reprocessing it over and over was, I believe, having a measurable contribution to overall time. Not huge, but enough that I added an include_guard() to avoid it (but later had to revert that as part of undoing a larger change).

Supporting non-global functions would be a significant change with potentially far-reaching consequences. I don’t know how feasible it would be to add support for such functionality, but I do think it would need to be thought through very carefully if attempted.