FetchContent / CMAKE_MAKE_PROGRAM error

I’m having an issue with Ninja and FetchContent.
I’m trying to create a shared cmake code repository at my company.

And I would like to use FetchContent to grab this shared cmake code.

So I have the following

 FetchContent_Declare(generic_cmake_modules
     GIT_REPOSITORY "...." 
     GIT_TAG "main" 
 ) 

 FetchContent_MakeAvailable(generic_cmake_modules)

However this results in this error message:

“CMake Error: CMake was unable to find a build program corresponding to “Ninja”. CMAKE_MAKE_PROGRAM is not set. You probably need to select a different build tool.”

This is because I set CMAKE_MAKE_PROGRAM (IE Ninja) in a toolchain which is inside the generic_cmake_modules repo.

Which results in this workaround where I set CMAKE_MAKE_PROGRAM before the FetchContent_Declare call.

 # Explanation of why this is necessary and what is happening. 
 # FetchContent tries to configure the cmake modules and fails as a result of CMAKE_MAKE_PROGRAM not being defined. 
 # Because we define CMAKE_MAKE_PROGRAM in our toolchain, which is inside the content we are fetching. Catch22. 
 # 
 # This works around the issue 
 if ( CMAKE_GENERATOR MATCHES "Ninja" ) 
     # This will get overriden by a later toolchain 
     set(CMAKE_MAKE_PROGRAM "C:/......./ninja/1.10.0/ninja.exe" CACHE FILEPATH "") 
 endif() 

However this isn’t optimal since I need to be in control of the version of Ninja the client uses.
The clients shouldn’t care about the Ninja version.

What should I do?

During the project() call at the top-level of the project, CMake runs some initial compilations using try_compile machinery to inspect the compiler. This requires the build tool (here, ninja) to be available at that point. You might be able to try initializing generic_cmake_modules before that point, but it’s not going to be easy as if that project does a project() call, you’ll have the same problem there.

This is all happening before the project() call takes place.

The goal of this module is to set the toolchain. Which contains CMAKE_MAKE_PROGRAM.

Roughly the logic goes

  • FetchContent generic_cmake_modules
  • include(Generic)
  • generic_set_toolchain() <- This sets CMAKE_TOOLCHAIN_FILE which sets CMAKE_MAKE_PROGRAM
  • project(foobar)

Hmm. What’s the backtrace on the code that expects CMAKE_MAKE_PROGRAM to be set then within that rough outline?

Cmake version 3.15

D:…\cmake-3.15.1-win64-x64\share\cmake-3.15\Modules\FetchContent.cmake

  execute_process(
    COMMAND ${CMAKE_COMMAND} ${generatorOpts} .
    RESULT_VARIABLE result
    ${outputOptions}
    WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}"
  )
  if(result)
    if(capturedOutput)
      message("${capturedOutput}")
    endif()
    message(FATAL_ERROR "CMake step for ${contentName} failed: ${result}") # <- Fails here
  endif()

Running --trace-expand on the above execute_process gives me this:

Running with expanded trace output on.
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeLists.txt(4):  cmake_minimum_required(VERSION 3.15.1 )     
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeLists.txt(10):  project(foobar_cmake_modules-populate NONE )  
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(34):  if(CMAKE_HOST_UNIX )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(74):  else()
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(75):  if(CMAKE_HOST_WIN32 )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(76):  if(DEFINED ENV{PROCESSOR_ARCHITEW6432} )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(78):  else()
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(79):  set(CMAKE_HOST_SYSTEM_PROCESSOR foobar64 ) 
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(88):  if(CMAKE_TOOLCHAIN_FILE )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(108):  if(CMAKE_SYSTEM_NAME )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(113):  elseif(CMAKE_VS_WINCE_VERSION )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(119):  else()
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(120):  set(CMAKE_SYSTEM_NAME Windows )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(121):  if(NOT DEFINED CMAKE_SYSTEM_VERSION )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(122):  set(CMAKE_SYSTEM_VERSION 10.0.18363 )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(124):  set(CMAKE_SYSTEM_PROCESSOR foobar64 )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(125):  set(CMAKE_CROSSCOMPILING FALSE )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(126):  set(PRESET_CMAKE_SYSTEM_NAME FALSE )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(129):  include(Platform/Windows-Determine OPTIONAL )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(131):  macro(ADJUST_CMAKE_SYSTEM_VARIABLES _PREFIX )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(160):  ADJUST_CMAKE_SYSTEM_VARIABLES(CMAKE_SYSTEM )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(132):  if(NOT CMAKE_SYSTEM_NAME )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(137):  if(CMAKE_SYSTEM_NAME MATCHES BSD.OS )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(142):  if(CMAKE_SYSTEM_NAME MATCHES kFreeBSD )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(147):  if(CMAKE_SYSTEM_NAME MATCHES CYGWIN )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(152):  set(CMAKE_SYSTEM Windows )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(154):  if(CMAKE_SYSTEM_VERSION )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(155):  set(CMAKE_SYSTEM Windows-10.0.18363 )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(161):  ADJUST_CMAKE_SYSTEM_VARIABLES(CMAKE_HOST_SYSTEM )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(132):  if(NOT CMAKE_HOST_SYSTEM_NAME )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(137):  if(CMAKE_HOST_SYSTEM_NAME MATCHES BSD.OS )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(142):  if(CMAKE_HOST_SYSTEM_NAME MATCHES kFreeBSD )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(147):  if(CMAKE_HOST_SYSTEM_NAME MATCHES CYGWIN )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(152):  set(CMAKE_HOST_SYSTEM Windows )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(154):  if(CMAKE_HOST_SYSTEM_VERSION )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(155):  set(CMAKE_HOST_SYSTEM Windows-10.0.18363 )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(165):  if(CMAKE_BINARY_DIR )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(167):  if(PRESET_CMAKE_SYSTEM_NAME )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(172):  else()
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(173):  file(APPEND D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/CMakeOutput.log The system is: Windows - 10.0.18363 - foobar64
 )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(179):  set(INCLUDE_CMAKE_TOOLCHAIN_FILE_IF_REQUIRED )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(180):  if(CMAKE_TOOLCHAIN_FILE )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeDetermineSystem.cmake(185):  configure_file(D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeSystem.cmake.in D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake @ONLY )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(1):  set(CMAKE_HOST_SYSTEM Windows-10.0.18363 )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(2):  set(CMAKE_HOST_SYSTEM_NAME Windows )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(3):  set(CMAKE_HOST_SYSTEM_VERSION 10.0.18363 )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(4):  set(CMAKE_HOST_SYSTEM_PROCESSOR foobar64 )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(8):  set(CMAKE_SYSTEM Windows-10.0.18363 )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(9):  set(CMAKE_SYSTEM_NAME Windows )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(10):  set(CMAKE_SYSTEM_VERSION 10.0.18363 )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(11):  set(CMAKE_SYSTEM_PROCESSOR foobar64 )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(13):  set(CMAKE_CROSSCOMPILING FALSE )
D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/3.15.1/CMakeSystem.cmake(15):  set(CMAKE_SYSTEM_LOADED 1 )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeNinjaFindMake.cmake(5):  find_program(CMAKE_MAKE_PROGRAM NAMES ninja-build ninja samu DOC Program used to build from build.ninja files. )
D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/CMakeNinjaFindMake.cmake(8):  mark_as_advanced(CMAKE_MAKE_PROGRAM )
CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
-- Configuring incomplete, errors occurred!
See also "D:/foobar_github/foobar_cmake_modules/tests/build/0/Debug/_deps/foobar_cmake_modules-subbuild/CMakeFiles/CMakeOutput.log".

CMake Error at D:/.../cmake-3.15.1-win64-x64/share/cmake-3.15/Modules/FetchContent.cmake:903 (message):
  CMake step for foobar_cmake_modules failed: 1

As I suspected, this is due to a project() call. You’ll need to do all of this before any project() call within the CMake codepath.

So basically the cmake workflow I want to create is just illegal. Because I’m trying to define CMAKE_MAKE_PROGRAM far too late.

Yes. I would suggest possibly bootstrapping it with a script to download a ninja at least before launching your build. Users are going to need to make a Visual Studio (or other compiler) environment available beforehand anyways, so I don’t think adding a script execution is too much to add, but that’s me.

Gotcha. I kinda figured this was the case.