CMakeLists - AddExternalProject cmake is not run correctly

Hi!

I am currently trying to add an external library to my project using AddExternalProject, but it does not seem to run cmake for the external project correctly (I don’t see cmake being run when I use CMAKE_VERBOSE_MAKEFILE).

When I manually run cmake in the build folder of the external project then everything works smoothly.

Here is my CMakeLists.txt for reference:

cmake_minimum_required(VERSION 3.0)
set(CMAKE_VERBOSE_MAKEFILE ON)
project(orlando)

# Paths for libdelfin
include(ExternalProject)
set(libdelfin_root_dir    "${CMAKE_CURRENT_LIST_DIR}/libdelfin")
set(libdelfin_build_dir   "${libdelfin_root_dir}/build")

set(libdelfin_lib_dir     "${libdelfin_root_dir}/build/lib")
set(libdelfin_include_dir "${libdelfin_root_dir}/build/include")

if(CMAKE_GENERATOR STREQUAL "Unix Makefiles")
# https://www.gnu.org/software/make/manual/html_node/MAKE-Variable.html
set(submake "$(MAKE)")
else() # Obviously no MAKEFLAGS. Let's hope a "make" can be found somewhere.
set(submake "make")
endif()

# Create build folder for libdelfin
execute_process(
  COMMAND ${CMAKE_COMMAND} -E make_directory ${libdelfin_build_dir}
  COMMAND ${CMAKE_COMMAND} -E make_directory ${libdelfin_include_dir}
  )

# Add libdelfin as external project
ExternalProject_Add(
  libdelfin_project
  PREFIX         ${libdelfin_root_dir}
  SOURCE_DIR     ${libdelfin_root_dir}
  BINARY_DIR     ${libdelfin_build_dir}
  CMAKE_ARGS     -GNinja
		 -DLIBDELFIN_USE_STACK
		 ..
  BUILD_COMMAND ${CMAKE_COMMAND} -E env ninja
  INSTALL_COMMAND ""
  TEST_COMMAND ""
  BUILD_BYPRODUCTS ${libdelfin_lib_dir}/libdelfin.a
  )

# Add main executable and make it dependend on libdelfin
add_executable(orlando orlando.c)
add_dependencies(orlando libdelfin_project)
target_include_directories(orlando PRIVATE ${libdelfin_include_dir})

When building I use the following commands:

mkdir build
cd build
cmake ..
make

in the main directory of my project. This always results in the following error message:

[ 18%] Performing build step for 'libdelfin_project'
cd /orlando/libdelfin/build && /usr/bin/cmake -E env ninja
ninja: error: loading 'build.ninja': No such file or directory

which makes sense, given that it was not generated. The build folder of the external library however does contain a lot of files which have been generated:

-rw-rw-r-- 1 florian florian  23K Jun 11 18:51 CMakeCache.txt
drwxrwxr-x 4 florian florian 4,0K Jun 11 18:51 CMakeFiles
-rw-rw-r-- 1 florian florian 1,8K Jun 11 18:51 cmake_install.cmake
-rw-rw-r-- 1 florian florian 800K Jun 11 18:51 compile_commands.json
-rw-r--r-- 1 florian florian 3,1K Jun 11 18:51 CPackConfig.cmake
-rw-r--r-- 1 florian florian 3,6K Jun 11 18:51 CPackSourceConfig.cmake
drwxrwxr-x 3 florian florian 4,0K Jun 11 18:51 include
drwxrwxr-x 2 florian florian 4,0K Jun 11 18:51 lib
-rw-rw-r-- 1 florian florian  55K Jun 11 18:51 Makefile
drwxrwxr-x 7 florian florian 4,0K Jun 11 18:51 src

This implies to me that it did execute cmake for my external library, but not the way I want it, so I wanted to manually test if when I ‘configure’ it myself it works, which it does. The command I am using to ‘manually configure’ the library is

cmake -GNinja -DLIBDELFIN_USE_STACK ..

I feel like I am missing something obvious here, which prevents cmake from doing what I want it to do. Does someone have an idea what I’m missing? Thanks in advance!

You can’t select the generator of the external sub-build. ExternalProject_Add() will add a -G option to use the same generator as the main project. In your case, your main project is using “Unix Makefiles” as its generator, so it is generating a Makefile, not a build.ninja file. Ideally, the cmake command should detect if it is given multiple -G options, but it appears that it is silently accepting multiple -G options and simply keeping the last one. That’s not unusual for Unix commands though, so it may be a deliberate choice. You also don’t need to add .., as ExternalProject_Add() will add the necessary path for you. Basically, CMAKE_ARGS should only be adding additional things beyond what ExternalProject_Add() will automatically add for you, it doesn’t replace them.

Also, the -DLIBDEFLIN_USE_STACK option is malformed. The -D option is for defining CMake cache variables, not compiler definitions, and the expected form of that is -Dname=value or -Dname:type=value. In your case, you don’t have the =value part, so I don’t know what this argument is doing, but it won’t be whatever you were expecting. The cmake command should really catch this as error.

I suggest you make the following changes:

  • Drop the -G option and .. from CMAKE_ARGS.
  • Fix the -DLIBDELFIN_USE_STACK option. Either make it a properly formed CMake cache variable definition, if that’s what you wanted it to do, or work out how to pass through a compiler definition if that’s what you wanted to do instead (I can’t advise on that, it will depend entirely on the way the libdelfin_project dependency is implemented).
  • Remove the BUILD_COMMAND completely.
  • Replace the execute_process() call with a file(MAKE_DIRECTORY ...) call. You shouldn’t need to create the ${libdelfin_build_dir} directory, just create the ${libdelfin_include_dir} directory and it should create parent directories as necessary.

Hi Craig!

Thank you for your answer! It was very helpful and I think I’m getting closer now.

However a minor part is still not working: I need the ExternalProject to be built before the main project as the main project requires include files, which will be generated by the ExternalProject. Is there a simple way to ensure that it is built first and the main project after it?

Thanks!

Your call to add_dependencies() should already be doing that for you. However, after you’ve built once, your external project won’t be rebuilt if you change anything in that external project. The main project won’t be aware of that change unless you add BUILD_ALWAYS YES to the ExternalProject_Add() call. Adding that option will mean your main project is never fully up-to-date, so doing a build will always result in some work being done, but that would be the trade-off for pulling in something via ExternalProject but you’re still actively changing that external content.