Documented criteria for build correctness?

I can think of at least one corner case where you may want to explicitly run cmake . before cmake --build .. With the Xcode generator, targets have a dependency on the ZERO_CHECK target, which is what re-runs cmake for you automatically if something changed. But the problem is that the rest of the build in that run still uses the old details from before cmake . gets run. If you build again, that rebuilds anything whose details changed, but explicitly re-running cmake . first if you know something about the project changed will be more robust and probably avoid more unnecessary rebuilds. I don’t recall if Visual Studio has a similar problem or not.

Outside of that, a well-formed project shouldn’t require the user to manually run cmake . themselves before a build. Indeed, CMake 3.20 even added the CONFIGURE_HANDLED_BY_BUILD keyword to ExternalProject_Add() which has the effect of giving the build step the responsibility of re-running the configure step if needed.

It’s sadly all too easy to create a project that doesn’t meet the “well-formed” requirement though. If a project uses file(WRITE) to create a source file or some other file that other things depend on, you have to look at what could change the contents of that file. Have you told CMake about those things such that if the contents could change, CMake knows it has to re-run cmake? A relatively common pattern is to use file(READ) to read a file, do some processing on that content and then write out the modified content using file(WRITE). If you forget to add the file that was read to the CMAKE_CONFIGURE_DEPENDS directory property, CMake won’t know about that dependency and won’t re-run cmake if the read-in file changes. If you instead use configure_file() or file(GENERATE ... INPUT ...) to achieve your goal, either of those two commands will record the dependency (and have the added advantage of not updating the timestamp of the destination file if the contents don’t actually change).

As for needing to run the configure step twice, I personally consider that a bug in the project. It implies logic that changes its outcome on subsequent runs. A couple of scenarios come to mind for how this might occur:

  • The project has incorrect ordering of its dependencies or options. For example, it may contain conditional logic that checks the value of a cache option that hasn’t been defined yet. It makes a certain decision on the first run in the absence of that cache option. On the second and subsequent run, the cache option now exists and it can result in a different condition for the logic that was relying on it. Defining dependent cache options in the wrong order is an example of this. I’m not sure if the CMakeDependentOption module helps avoid or contributes to this situation.

  • Setting a cache variable’s value can remove a normal variable of the same name from the current scope in a few specific scenarios. Some cases where this occurs are if the cache variable doesn’t yet exist or it has been defined without any type. On the first run, the regular variable is removed and the cache variable is created/updated. In the next run, the cache variable won’t be touched because it has a value and a type, so any regular non-cache variable will be left alone too. Because a non-cache variable takes precedence over a cache variable of the same name, this can result in different behavior between the two runs if the non-cache and cache variables have different values.

2 Likes