Migrating a configurable autotools-based build to CMake

I originally posted this on StackOverflow, I figured it would be helpful to post it here, too.

I am migrating a large legacy codebase from autotools to CMake, and we have some behaviour that we’d like to preserve. However, my naive initial reimplementation of the build system in CMake has resulted in the entire project being rebuilt every time cmake is invoked. I believe this is happening because of the way that include_directories is being used, along with a lack of cache variables, but I’d like to double-check here if a better solution exists than the one I’m thinking of.

The original reimplementation was done in CMake 2.8 (corporate slowness, hooray), but I’ve managed to get hold of 3.14, so I’ve tried to tidy it up a little. I’d like to use some more modern features if possible.

I should also add that it would be very preferable to not rely on things like environment variables. Ultimately, we would like to have Visual Studio as an option available to developers when working on this, as the project itsellf builds on Linux, and VS has the capability to understand CMake projects and build remotely on a Linux box.


Legacy Autotools setup

conf.sh:

#!/bin/sh
DIR_=`dirname $0`
${DIR_}/configure \
  --some-flag=yes \
  --some-other-flag=ok \
  --some-dependency=/home/deps/dependencyOne \
  --some-other-dependency=/home/deps/dependencyTwo \
"$@"

exit %?

configure.in contains the decoding logic to set these dependencies up:

AC_ARG_ENABLE(some-dependency),
[
if test "x$some_dependency" != "xno" ; then
  if test -d "${enableval}"; then
    DEPONE_DIR=$enableval
  else
    AC_MSG_ERROR("Must specify dependencyOne directory!")
  fi
  DEPONE_INCLUDES="-I$DEPONE_DIR/include"
  DEPONE_LDFLAGS="-L$DEPONE_DIR/lib -ldepone"

  #etcetera
fi
]

Running ./conf.sh from the desired build directory generates the Makefiles and such. After this, we run make. If we change a source file, it only rebuilds that source and its dependees (dependors? Is there a word for this). If we change a Makefile.am, again, it’s smart enough to not rebuild everything.


Current CMakeLists.txt setup:

We want to be able to switch dependencies on and off in the build, and the way we do this is through this configure script. I originally recreated the existing setup closely in CMake as shown below:

Config script:

#!/bin/bash
libs=(
  -Dsome-dependency=/home/deps/dependencyOne
  -Dsome-other-dependency=/home/deps/dependencyTwo
)

args=(
  ${libs[@]}
  -Dsome-flag=yes
  -Dsome-other-flag=ok
)

cmake "${args[@]}"

Root CMakeLists.txt:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12)
PROJECT(myProject)

#some language-specific setup (standard etc)

if(some-dependency)
  link_directories(${some-dependency}/lib)
  include_directories(${some-dependency}/include)
endif()

if(some-other-dependency)
  link_directories(${some-other-dependency}/lib)
  include_directories(${some-other-dependency}/include)
endif()

I noticed that if I switch off a line in the config script, running cmake will have cached the value and still act as if it was enabled, which I don’t want. So I committed this cardinal sin:

End of root CMakeLists.txt:

unset(some-dependency CACHE)
unset(some-other-dependency CACHE)

Now, every time I run the configure script (and by extension cmake), it rebuilds everything when I run make. My guess is that this happens because of include_directories being reset every time, resulting in everything being marked as dirty.

My supposed solution to this is twofold:

  • Use target_include_directories
  • Check if the value supplied from the config script via -D matches an existing CACHE entry, and if so, do nothing. I don’t think this will work, given -D sets a variable in cache.

Is there a better way of getting this done? I get the impression that my attempt at it is fairly naive and simplistic, and if a better solution exists, I’d really love to know about it. I bought “Professional CMake” by Craig Scott, but couldn’t glean enough to see a straightforward solution to this.

Possible? If the values are made the same each time, it should no-op correctly. You can use make -d to have it say why it thinks each thing needs to rerun. If you use the Ninja generator ninja -d explain does it more concisely.

You can instead do something like:

if (NOT current_value STREQUAL previous_value)
  unset(current_value CACHE)
endif ()
set(previous_value "${current_value}" CACHE INTERNAL "")

to instead only remove the cache variable when its value changes. But I doubt that this is the actual problem here. I would instead suspect a generated file that is included at some core is written manually using file(WRITE) instead of using something like configure_file.

Oh, you want the absence of a flag to act as if it is the default. There’s no easy way to do that without unset(CACHE) AFAIK.

I think you only need to do the first part of proposed solution: switch to using target_include_directories.

If I remember correctly using include_directories() will cause all the Makefiles to change when running the configure script; this causes all the targets to rebuild because its Makefile gets updated.

Thanks for the replies all.

I’ve decided to go with the following for now:

Root CMakeLists.txt:

...
set(dependencyOne /home/deps/dependencyOne CACHE PATH "blah")
option(enableDepOne "Enable DependencyOne" OFF)

if(enableDepOne)
  set(depOneIncludes ${dependencyOne}/include CACHE PATH " ")
  ...etc
endif()
...

Various other CMakeLists.txt:

add_library(some-library STATIC someSource.cpp)
target_include_directories(some-library PUBLIC ${depOneIncludes})

Then invoking cmake with:

cmake -DenableDepOne=ON

The invocation command will be in a separate bash script, and having explicitly commented-out or OFF hardly makes a difference. Having to go to the CMakeLists.txt to check for file paths in use is a little annoying, but this is the best solution I’ve managed to come up with so far.
As fdk17 says, target_include_directories will avoid touching every single Makefile, and only those that are relevant as anything changes.
I will investigate Ben’s suggestion that file(WRITE) is causing some issue (I am using that). configure_file seems to be the obvious better choice for this.